<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://chodounsky.com/">
  <id>https://chodounsky.com/</id>
  <title>The diary of a software developer :: Jakub Chodounský</title>
  <updated>2024-02-16T03:07:33Z</updated>
  <link rel="alternate" href="https://chodounsky.com/" type="text/html"/>
  <link rel="self" href="https://chodounsky.com/feed.xml" type="application/atom+xml"/>
  <author>
    <name>Jakub Chodounský</name>
    <uri>https://chodounsky.com</uri>
  </author>
  <entry>
    <id>tag:chodounsky.com,2024-02-16:/2024/02/16/social-media-preview-images-for-static-website/</id>
    <title type="html">Social media preview images for static website</title>
    <published>2024-02-16T03:07:33Z</published>
    <updated>2024-02-16T03:07:33Z</updated>
    <link rel="alternate" href="https://chodounsky.com/2024/02/16/social-media-preview-images-for-static-website/" type="text/html"/>
    <content type="html">&lt;p&gt;In this article, we’ll generate social media preview images for a static website in Ruby. In the end, we’ll be able to expose them in &lt;code&gt;og:image&lt;/code&gt; and &lt;code&gt;twitter:image&lt;/code&gt; meta tags.&lt;/p&gt;

&lt;p&gt;We’ll add them to a blog generated by &lt;a href="https://nanoc.app"&gt;&lt;code&gt;nanoc&lt;/code&gt;&lt;/a&gt; framework. But the idea translates to other static site generators like Jekyll or even dynamic frameworks like Rails.&lt;/p&gt;

&lt;p&gt;Here’s what we’ll have to do:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;generate an image,&lt;/li&gt;
  &lt;li&gt;add it to &lt;code&gt;nanoc&lt;/code&gt; compilation and&lt;/li&gt;
  &lt;li&gt;update the layout.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id="generate-image"&gt;Generate image&lt;/h2&gt;

&lt;p&gt;We’ll take a background image and dynamically add a text on top of it. For that, we will use &lt;code&gt;rmagick&lt;/code&gt; gem. And then the &lt;code&gt;Magick::Draw#annotate&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;The dimensions of the background image should be 1200x630px and under 8MB according to the &lt;a href="https://developers.facebook.com/docs/sharing/webmasters/images/"&gt;spec&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lastly, we want to make sure that the text fits and if it’s longer it wraps properly.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;require "rmagick"

class OpenGraphImage
  def initialize(title, output_path)
    @title = title
    @output_path = output_path
  end

  def generate
    image = Magick::Image.read("background.jpg").first

    create = Magick::Draw.new

    create.annotate(image, 0, 0, 3, 0, word_wrap(@title)) do |txt|
      txt.font = 'SF-Pro-Display-Bold.otf'
      txt.pointsize = 65
      txt.font_weight = Magick::BoldWeight
      txt.fill = 'white'
      txt.gravity = Magick::CenterGravity
    end

    image.write("jpg:" + @output_path)
  end

  private

  def word_wrap(line)
    return line if line.length &amp;lt;= 26
    line.gsub(/(.{1,26})(\s+|$)/, "\\1\n").strip
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="add-to-nanoc-compilation"&gt;Add to nanoc compilation&lt;/h2&gt;

&lt;p&gt;Nanoc uses filters to manipulate files, so let’s create one that uses our &lt;code&gt;OpenGraphImage&lt;/code&gt; class. We are converting markdown files to images so the type of the filter is &lt;code&gt;text: :binary&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For the text, we’re using the article’s title.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class GenerateOpenGraphImage &amp;lt; Nanoc::Filter
  identifier :generate_open_graph_image
  type text: :binary

  def run(content, params = {})
    OpenGraphImage.new(item[:title], output_filename).generate
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then in our &lt;code&gt;Rules&lt;/code&gt; file, we’ll add another compilation rule. We can’t have two rules processing the same file unless we add a &lt;code&gt;rep&lt;/code&gt; to it. Since we’re generating jpegs let’s use &lt;code&gt;rep:jpg&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;compile "/articles/**/*.md", rep: :jpg do
  filter :generate_open_graph_image

  write "/og/#{item.identifier.without_ext}.jpg"
end
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="update-layout"&gt;Update layout&lt;/h2&gt;

&lt;p&gt;And the last thing missing is adding the new image paths to the head meta tags of the default layout.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;meta property="twitter:image" content="https://example.com&amp;lt;%= "/og/#{item.identifier.without_ext}.jpg" %&amp;gt;" /&amp;gt;
&amp;lt;meta property="og:image" content="https://example.com&amp;lt;%= "/og/#{item.identifier.without_ext}.jpg" %&amp;gt;"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And we’re done. For each article, we are automatically generating the social media preview images.&lt;/p&gt;

&lt;p&gt;A useful tool for debugging these previews are Facebook’s &lt;a href="https://developers.facebook.com/tools/debug/"&gt;Sharing Debugger&lt;/a&gt; and &lt;a href="https://www.opengraph.xyz/url/https%3A%2F%2Fchodounsky.com%2F2024%2F02%2F02%2Fsemantic-search-in-rails%2F"&gt;Open Graph Preview&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>tag:chodounsky.com,2024-02-02:/2024/02/02/semantic-search-in-rails/</id>
    <title type="html">Semantic search in Rails</title>
    <published>2024-02-02T04:09:50Z</published>
    <updated>2024-02-02T04:09:50Z</updated>
    <link rel="alternate" href="https://chodounsky.com/2024/02/02/semantic-search-in-rails/" type="text/html"/>
    <content type="html">&lt;p&gt;Semantic search allows you to find data based on meaning. This is different from a traditional lexical search where you are looking to match exact-ish keywords.&lt;/p&gt;

&lt;p&gt;Searching by meaning should improve the result’s accuracy and the experience. A user can focus on their intent and not on “hacking” the search tool to get decent results.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note: it’s easy to &lt;a href="/2015/05/06/full-text-search-in-rails-with-pg-search/"&gt;implement a full-text lexical search&lt;/a&gt; in your Rails app.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To add semantic search into Rails app here’s what we’ll have to do.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Assign meaning to our data.
    &lt;ul&gt;
      &lt;li&gt;Generate embeddings with &lt;a href="https://platform.openai.com/docs/guides/embeddings"&gt;OpenAI API&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Store meaning in Postgres.
    &lt;ul&gt;
      &lt;li&gt;Use &lt;a href="https://github.com/pgvector/pgvector"&gt;&lt;code&gt;pg_vector&lt;/code&gt;&lt;/a&gt; extension.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Assign meaning to a search query.
    &lt;ul&gt;
      &lt;li&gt;Generate embeddings for the query with OpenAI.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Find the most relevant data.
    &lt;ul&gt;
      &lt;li&gt;Using &lt;a href="https://github.com/ankane/neighbor"&gt;&lt;code&gt;nearest_neighbor&lt;/code&gt;&lt;/a&gt; gem.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this example, we’ll build a semantic search over a database of blog articles with text content.&lt;/p&gt;

&lt;h2 id="assigning-meaning"&gt;Assigning meaning&lt;/h2&gt;

&lt;p&gt;To find an article’s meaning we’ll use artificial intelligence and one of the large language models. In this case, we are using OpenAI’s API. It is simple, cheap and easy to deploy on an existing infrastructure.&lt;/p&gt;

&lt;h3 id="embeddings"&gt;Embeddings&lt;/h3&gt;

&lt;p&gt;We’ll use their embeddings API. You can pass some text and you’ll get its embeddings back.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Langchain::LLM::OpenAI.new(api_key: ...).embed(text: content).embedding
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note: I’m using a built-in OpenAI client from &lt;code&gt;langchain&lt;/code&gt; here. The primary use case for &lt;code&gt;langchain&lt;/code&gt; will be chunking discussed a little further below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Embeddings are a mathematical representation of text relatedness. It’s a vector of decimal numbers where the distance between them measures how related the two strings are.&lt;/p&gt;

&lt;p&gt;In a Rails console, they look like an array with a bunch of decimal numbers.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[0.005204988,
 0.029841444,
 0.016162278,
 -0.027387716,
...]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, we can take two of these and calculate the &lt;a href="https://en.wikipedia.org/wiki/Euclidean_distance"&gt;distance&lt;/a&gt; between them. It will be a single decimal number between 0 and 1. The closer it is to 1 the more related they are to each other.&lt;/p&gt;

&lt;h3 id="chunking"&gt;Chunking&lt;/h3&gt;

&lt;p&gt;If our articles were short and could fit into the model’s context we could move on to the next section. But usually, they are larger and we have to split the text into smaller parts – chunks.&lt;/p&gt;

&lt;p&gt;To chunk an article up we’ll use &lt;a href="https://github.com/andreibondarev/langchainrb"&gt;&lt;code&gt;langchain&lt;/code&gt;&lt;/a&gt; gem. It is a Swiss army knife for text manipulation in LLM applications.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Langchain::Chunker::RecursiveText.new(
  text_content,
  chunk_size: 1536,
  chunk_overlap: 200,
  separators: ['\n\n']
).chunks
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The model that we will be using has a context size of 1536 so that’s the chunk size for us.&lt;/p&gt;

&lt;p&gt;Once we have chunks we can generate embeddings for each one of them through OpenAI.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;chunks.map { |chunk| open_ai.embed(text: content).embedding }
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="storing-meaning-in-postgres"&gt;Storing meaning in Postgres&lt;/h2&gt;

&lt;p&gt;Now, we need to store the chunks and link them back to our articles. We’ll create a standard ActiveRecord model. But first, we need to figure out how to store embeddings in Postgres.&lt;/p&gt;

&lt;p&gt;For that, we’ll use &lt;a href="https://github.com/pgvector/pgvector"&gt;&lt;code&gt;pg_vector&lt;/code&gt;&lt;/a&gt; extension. We need to &lt;a href="https://github.com/pgvector/pgvector?tab=readme-ov-file#installation"&gt;install&lt;/a&gt; it first as it doesn’t come with the default distribution. After that, we can enable the extension in a standard migration.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def change
  enable_extension "vector"
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And with that, we can create our model to store the chunks with embeddings using a new &lt;code&gt;vector&lt;/code&gt; datatype.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;create_table :chunks do |t|
  t.references :article, null: false, foreign_key: true
  t.text :content
  t.vector :embedding, limit: 1536

  t.timestamps
end
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="finding-relevant-data"&gt;Finding relevant data&lt;/h2&gt;

&lt;p&gt;Now that we have all the articles chunked up with embeddings we need to be able to query them.&lt;/p&gt;

&lt;p&gt;Here, we’ll use &lt;a href="https://github.com/ankane/neighbor"&gt;&lt;code&gt;nearest_neighbor&lt;/code&gt;&lt;/a&gt; gem.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Chunk &amp;lt; ApplicationRecord
  has_neighbors :embedding
  ...
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After adding &lt;code&gt;has_neighbors&lt;/code&gt; into our model we’ll be able to use the &lt;code&gt;.nearest_neighbors&lt;/code&gt; class method to query embeddings we saved earlier.&lt;/p&gt;

&lt;p&gt;We can hack everything together and turn the user’s search query into embeddings. Pass it into the &lt;code&gt;.nearest_neighbors&lt;/code&gt; method and map the results back to articles.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;query_embedding = open_ai.embed(text: query).embedding
ids = Chunk.nearest_neighbors(:embedding, query_embedding, distance: "cosine").first(20).map(&amp;amp;:article_id).uniq
Article.where(id: ids)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And that’s it. We can search our article database by meaning instead of relying on keyword matching. And as a bonus, we’ve integrated AI with LLM into our Rails app. The board is excited and our company’s valuation increased 2-3x.&lt;/p&gt;

&lt;hr&gt;

&lt;h3 id="how-much-does-it-cost"&gt;How much does it cost?&lt;/h3&gt;

&lt;p&gt;Not much. But it depends on the volume and the model used.&lt;/p&gt;

&lt;p&gt;For generating embeddings for ~600 blog articles I paid under $0.2. Since I’m the only one searching the database the cost of search queries is negligible.&lt;/p&gt;

&lt;h3 id="setup-on-circleci"&gt;Setup on CircleCI&lt;/h3&gt;

&lt;p&gt;Surprisingly, the most time-consuming task was getting &lt;code&gt;pg_vector&lt;/code&gt; installed on CircleCI.&lt;/p&gt;

&lt;p&gt;The extension needs a few additional steps to install. That’s straightforward on a VPS or your dev machine but not on CircleCI.&lt;/p&gt;

&lt;p&gt;CircleCI has a primary container where your tasks are running. And there are supporting containers, like databases, for everything else. However, you can’t run an arbitrary script in your setup phase on a supporting container.&lt;/p&gt;

&lt;p&gt;The best way around that is to create a docker image with &lt;code&gt;pg_vector&lt;/code&gt; installed. And then, use it for the supporting container.&lt;/p&gt;

&lt;p&gt;In the end, I forked &lt;code&gt;cimg/postgres&lt;/code&gt; convenience image from &lt;a href="https://github.com/CircleCI-Public/cimg-postgres"&gt;CircleCI repository&lt;/a&gt;. Then, I added the installation steps after the main Postgres setup.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;RUN cd /tmp &amp;amp;&amp;amp; \
    git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git &amp;amp;&amp;amp; \
    cd pgvector &amp;amp;&amp;amp; \
    make -j $(nproc) &amp;amp;&amp;amp; \
    PATH=$PATH make install
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Lastly, I published the image to a public Docker repository. And used it in my &lt;code&gt;.circleci/config.yml&lt;/code&gt; instead of the original convenience image.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>tag:chodounsky.com,2020-04-07:/2020/04/07/lastpass-slows-down-your-browser/</id>
    <title type="html">LastPass slows down your browser</title>
    <published>2020-04-07T05:25:39Z</published>
    <updated>2020-04-07T05:25:39Z</updated>
    <link rel="alternate" href="https://chodounsky.com/2020/04/07/lastpass-slows-down-your-browser/" type="text/html"/>
    <content type="html">&lt;p&gt;My old 2013 Macbook seemed a bit slow in the last few weeks. Although it handled most of the tasks well it became sluggish when browsing the internet. Well, surfing the web is one of the more compute-intensive tasks these days so maybe it is time to buy a new machine, I thought. But then while working on &lt;a href="https://programmingdigest.net/?utm_source=blog&amp;amp;utm_campaign=lastpass"&gt;Programming Digest&lt;/a&gt; newsletter over the weekend I noticed an issue coming from one of the Chrome extensions after running Lighthouse audit.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="A local extension adds more than 0.5s to the page load. Recaptcha is a story for another time." src="/images/posts/2020/lastpass-lighthouse.png" height="632" width="1428"&gt;
  &lt;figcaption&gt;A local extension adds more than 0.5s to the page load. Recaptcha is a story for another time.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I jumped into &lt;code&gt;chrome://extensions&lt;/code&gt; to check which one is causing the troubles. It turned out to be LastPass – a password manager I’ve been using for quite a while.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="LastPass extension's id matches the one from the script." src="/images/posts/2020/lastpass-extension.png" height="262" width="480"&gt;
  &lt;figcaption&gt;LastPass extension's id matches the one from the script.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Let’s dig deeper and run a few &lt;a href="https://browserbench.org/Speedometer2.0/"&gt;Speedometer 2.0&lt;/a&gt; benchmarks. Firstly, let’s disable all extensions to get a baseline.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Speedometer shows 74.5 runs per minute." src="/images/posts/2020/speedometer-no-extensions.png" height="765" width="1000"&gt;
  &lt;figcaption&gt;Speedometer shows 74.5 runs per minute.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Secondly, let’s enable only LastPass and run the test again.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Dropping to 45.7 runs per minute with LastPass enabled." src="/images/posts/2020/speedometer-lastpass-only.png" height="771" width="1010"&gt;
  &lt;figcaption&gt;Dropping to 45.7 runs per minute with LastPass enabled.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;That’s a huge difference. It doesn’t look great for LastPass. It turned out I’m &lt;a href="https://twitter.com/_pastelsky/status/1180864405648502784"&gt;not the first one&lt;/a&gt; to notice and there are numerous tickets raised in LastPass’s support forum. I tried to disable auto-fill and experimented with a few other settings but the only way to get rid of the negative performance impact was disabling the extension and only load it on demand.&lt;/p&gt;

&lt;p&gt;I’ve been frustrated with LastPass’s user experience for quite some time. Sharing passwords and using it in a team is a huge pain. And with this (why do you need to run 12Mb of JavaScript on every page) and questionable privacy policy (“we can collect whatever data we want and share it with whoever we please”) it was time to look for an alternative.&lt;/p&gt;

&lt;p&gt;A tech community seems to be praising &lt;a href="https://bitwarden.com/"&gt;Bitwarden&lt;/a&gt; these days so I decided to give it a try. The &lt;a href="https://help.bitwarden.com/article/import-from-lastpass/"&gt;migration from LastPass&lt;/a&gt; was straightforward. It is free for individuals and reasonably priced for families ($1/month). UI is nice and it works well on an iPad and my Android phone. When I got it all set the first thing I did was running the benchmark.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Bitwarden doesn't impact the browsing experience with only a small drop to 73.8 runs per minute." src="/images/posts/2020/speedometer-bitwarden-only.png" height="791" width="1028"&gt;
  &lt;figcaption&gt;Bitwarden doesn't impact the browsing experience with only a small drop to 73.8 runs per minute.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Bitwarden seems to be great so far and I might save some money on a new computer. This whole experiment made me think about which extensions I use daily and how they can influence my productivity and privacy.&lt;/p&gt;

&lt;h2 id="note"&gt;Note&lt;/h2&gt;

&lt;p&gt;I ran the benchmark for a few other extensions as well and also tested Safari and iPad.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Chrome
    &lt;ul&gt;
      &lt;li&gt;No extensions: 75&lt;/li&gt;
      &lt;li&gt;Bitwarden: 73.8&lt;/li&gt;
      &lt;li&gt;Vimium: 70&lt;/li&gt;
      &lt;li&gt;Pocket: 75&lt;/li&gt;
      &lt;li&gt;uBlock Origin: 68&lt;/li&gt;
      &lt;li&gt;Homebrewed extension: 73.2&lt;/li&gt;
      &lt;li&gt;All of them together: 68.9&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Safari: 87.1&lt;/li&gt;
  &lt;li&gt;iPad Air 3: 135&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;uBlock Origin has some impact on the performance but it also blocks a ton of tracking and advertising traffic so I assume there is a net gain. On the other hand, Vimium is a harder sell. It gives me vim bindings in Chrome but I’m going to try live without it as I’m not a heavy user.&lt;/p&gt;

&lt;p&gt;Safari is faster. But I’m not ready to leave Chrome as I use multiple devices and a fair bit of Google services. And wow. No wonder I like using my iPad for internet browsing. It is lightning fast!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>tag:chodounsky.com,2020-01-14:/2020/01/14/suggesting-email-address-using-string-similarity-algorithm/</id>
    <title type="html">Suggesting email address using string similarity algorithm</title>
    <published>2020-01-14T02:56:07Z</published>
    <updated>2020-01-14T02:56:07Z</updated>
    <link rel="alternate" href="https://chodounsky.com/2020/01/14/suggesting-email-address-using-string-similarity-algorithm/" type="text/html"/>
    <content type="html">&lt;p&gt;&lt;img src="/images/posts/2020-01-13-suggesting-correct-email-address-with-trigrams.png" alt="Suggesting a correct domain for mistyped email address with trigrams" height="124" width="533"&gt;&lt;/p&gt;

&lt;p&gt;Recently, we noticed a large number of unconfirmed accounts. Those were not spam accounts they looked like legit users. Are we losing customers that early in the funnel? When we looked closely we noticed a similar pattern:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;john.doe@gnail.com
jane.doe@yahoo.co.nz
tim@outlook.con
susan@gmail.co.nz
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;All of those email addresses have a mistyped domain. That’s not great. I could imagine a customer refreshing their inbox, waiting for a confirmation email, and cursing the internet.&lt;/p&gt;

&lt;p&gt;What could we do to improve the onboarding experience? What if we could detect typos and suggest a correct email for them?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Did you mean john.doe@gmail.com?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That would be really cool. And luckily for us there is a few neat algorithms that can help us with that.&lt;/p&gt;

&lt;p&gt;In computer science this is an exercise of approximate string matching. And we will use a &lt;a href="https://en.wikipedia.org/wiki/String_metric"&gt;string distance&lt;/a&gt; (or sometimes called a string metric) to quantify how close two words – email domains in our case – are to each other.&lt;/p&gt;

&lt;h2 id="levensthein-distance"&gt;Levensthein Distance&lt;/h2&gt;

&lt;p&gt;The most common string metric is &lt;a href="https://en.wikipedia.org/wiki/Levenshtein_distance"&gt;Levenshtein distance&lt;/a&gt; (also called edit distance). It tells you the minimum number of edits – insertion, deletion, substitution – that need to take place to get to the same word.&lt;/p&gt;

&lt;p&gt;For example &lt;code&gt;gnail.com&lt;/code&gt; and &lt;code&gt;gmail.com&lt;/code&gt; would have Levensthein distance of 1. You need to substitute one character to get to the same word. Those two strings are very similar.&lt;/p&gt;

&lt;p&gt;We can normalise the distance between 0.0 and 1.0 where 0.0 is not similar at all and 1.0 is an exact match. That’s a good idea to do for different string lengths. For that, we divide the edit distance by the length of the longest string and subtract that from 1. For our example it would end up to be 0.88.&lt;/p&gt;

&lt;p&gt;Levensthein distance is usually implemented with &lt;a href="https://en.wikipedia.org/wiki/Dynamic_programming"&gt;dynamic programming&lt;/a&gt; using a variation of &lt;a href="https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm"&gt;Wagner-Fischer algorithm&lt;/a&gt;. Below is a Javascript version from &lt;a href="https://gist.github.com/andrei-m/982927"&gt;Andrei Mackenzie&lt;/a&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var getEditDistance = function(a, b){
  if(a.length == 0) return b.length;
  if(b.length == 0) return a.length;

  var matrix = [];
  var i;

  for(i = 0; i &amp;lt;= b.length; i++){
    matrix[i] = [i];
  }

  var j;
  for(j = 0; j &amp;lt;= a.length; j++){
    matrix[0][j] = j;
  }

  for(i = 1; i &amp;lt;= b.length; i++){
    for(j = 1; j &amp;lt;= a.length; j++){
      if(b.charAt(i-1) == a.charAt(j-1)){
        matrix[i][j] = matrix[i-1][j-1];
      } else {
        matrix[i][j] = Math.min(matrix[i-1][j-1] + 1,
                                Math.min(matrix[i][j-1] + 1,
                                         matrix[i-1][j] + 1));
      }
    }
  }

  return matrix[b.length][a.length];
};
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="trigram"&gt;Trigram&lt;/h2&gt;

&lt;p&gt;Levensthein distance is a nifty algorithm but we found it didn’t work particularly well for our dataset. Also, it not easy to scale but for this use case it doesn’t matter.&lt;/p&gt;

&lt;p&gt;There is a few other alternatives. We decided to go for &lt;a href="https://en.wikipedia.org/wiki/Jaccard_index"&gt;Jaccard distance&lt;/a&gt; over &lt;a href="https://en.wikipedia.org/wiki/Trigram"&gt;Trigram&lt;/a&gt; vectors. It sounds scary but don’t be afraid.&lt;/p&gt;

&lt;p&gt;Trigrams are three consecutive letter groups from a string. They are often used in natural language processing as they are relatively cheap to make and provide you with additional context of neighbouring characters.&lt;/p&gt;

&lt;p&gt;Firstly, we decompose a string into trigrams.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;trigram('hello');
['  h', ' he', 'hel', 'ell', 'llo', 'lo ']
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And the code for the above.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var trigram = function(string) {
  const padded = `  ${string} `.toLowerCase();

  let gramStart = 0;
  let gramEnd = 3;
  const grams = [];

  while (gramEnd &amp;lt;= padded.length) {
    grams.push(padded.substring(gramStart, gramEnd));
    gramStart += 1;
    gramEnd += 1;
  }

  return grams;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After that, we can calculate the distance. It is a ratio of matching trigrams to their union.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var distance = function(aGrams, bGrams) {
  const matches = aGrams.filter(value =&amp;gt; bGrams.includes(value));
  const uniqueGrams = [...new Set(aGrams.concat(bGrams))];
  return Number((matches.length / uniqueGrams.length).toFixed(2));
}

var compare = function(a, b) {
  const aGrams = trigram(a);
  const bGrams = trigram(b);
  return distance(aGrams, bGrams);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;With the final number we need to decide what threshold is a possible match. I’d like to say that we did an extensive testing on our dataset to get the perfect distance but we haven’t.&lt;/p&gt;

&lt;p&gt;Instead, we looked at Postgres &lt;a href="https://www.postgresql.org/docs/current/pgtrgm.html"&gt;pg_trgm&lt;/a&gt; extension and used their default of &lt;code&gt;0.3&lt;/code&gt;. And it worked out great.&lt;/p&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Last step is hooking these functions up to our registration form. We extract the domain from the input, lowercase it, and remove any white space.&lt;/p&gt;

&lt;p&gt;Then, we run it against a dictionary of our confirmed email domains that have at least a few users registered. That covers most of our basis and runs pretty quickly on a client.&lt;/p&gt;

&lt;p&gt;And with that, we save a customer or two from abandoning the registration.&lt;/p&gt;

&lt;h2 id="resources"&gt;Resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://medium.com/@ethannam/understanding-the-levenshtein-distance-equation-for-beginners-c4285a5604f0"&gt;Understanding Levensthein Distance for Beginners&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.npmjs.com/package/js-levenshtein"&gt;js-levensthein&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.67.9369&amp;amp;rep=rep1&amp;amp;type=pdf"&gt;N-gram similarity and distance&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://pganalyze.com/blog/similarity-in-postgres-and-ruby-on-rails-using-trigrams"&gt;Similarity in Postgres and Rails using Trigrams&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/pgtrgm.html"&gt;pg_trgm&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://bergvca.github.io/2017/10/14/super-fast-string-matching.html"&gt;Super fast string matching in Python&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://medium.com/@appaloosastore/string-similarity-algorithms-compared-3f7b4d12f0ff"&gt;String Similarity Algorithms Compared&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.cs.cmu.edu/~wcohen/postscript/ijcai-ws-2003.pdf"&gt;A Comparison of String Distance Metrics for Name-Matching Tasks&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.joyofdata.de/blog/comparison-of-string-distance-algorithms/"&gt;Comparison of String Distance Algorithms&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <id>tag:chodounsky.com,2019-10-21:/2019/10/21/the-great-computer-experiment-ruby-edition/</id>
    <title type="html">The Great Computer Experiment: Ruby Edition</title>
    <published>2019-10-21T08:53:19Z</published>
    <updated>2019-10-21T08:53:19Z</updated>
    <link rel="alternate" href="https://chodounsky.com/2019/10/21/the-great-computer-experiment-ruby-edition/" type="text/html"/>
    <content type="html">&lt;p&gt;Can a second-hand desktop computer from 2013 run specs? How much does a touch bar speed up an integration suite? Do I need 6 cores to run &lt;code&gt;rails new blog&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;In this article I’ll put 4 machines and 5 different operating systems through a true test. What am I trying to achieve? I don’t know. But I intend to share my experience with you.&lt;/p&gt;

&lt;h2 id="computers"&gt;Computers&lt;/h2&gt;

&lt;p&gt;Recently, I’ve been playing with different computers and operating systems. I used to be a C# developer on Windows for quite a few years and after switching to Ruby I started using Macs. The direction Apple took with its latest laptops made me explore other options and I tried Linux on a desktop PC. And I was surprised how powerful and cheap desktops are these days.&lt;/p&gt;

&lt;p&gt;Here are the machines that feature in the showdown. Prices are for New Zealand.&lt;/p&gt;

&lt;h3 id="custom-built-pc-2019"&gt;Custom-built PC (2019)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;i7-8700 (3.2-4.6Ghz, 6 cores with 12 threads)&lt;/li&gt;
  &lt;li&gt;32GB RAM&lt;/li&gt;
  &lt;li&gt;Samsung 970 EVO Plus NVMe SSD&lt;/li&gt;
  &lt;li&gt;Ubuntu 18.04&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Price: $1 500 ($950 USD) new&lt;/p&gt;

&lt;p&gt;This is my main office workstation. It’s custom-built with decent components but nothing lavishly outrageous. It’s a perfect compromise of snappy performance and cost. It runs basic Ubuntu 18.04 installation with no customisations. Good reliable work horse.&lt;/p&gt;

&lt;h3 id="macbook-pro-13-2013"&gt;Macbook Pro 13” (2013)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;i7-4558U (2.8-3.2GHz, 2 cores with 4 threads)&lt;/li&gt;
  &lt;li&gt;8GB RAM&lt;/li&gt;
  &lt;li&gt;macOS 10.14 Mojave&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Price: ~$700 ($450 USD) second-hand&lt;/p&gt;

&lt;p&gt;The price is for a similar machine on the second-hand market in New Zealand. This has been my day-to-day machine for the last 6 years. I still use it when working from home or on the go.&lt;/p&gt;

&lt;p&gt;Double amount of RAM would be better and maybe more cores but I’m not planning replacing it any time soon. It is a solid laptop with a great keyboard and beautiful screen. Recently, I was tempted by some of the new ThinkPads but I’m too scared of Linux on a laptop. If there is something I don’t want to do it’s tinkering with touchpad drivers, keyboard backlighting, and wonky wi-fi.&lt;/p&gt;

&lt;h3 id="budget-dell-2013"&gt;Budget Dell (2013)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;i5-4570 (3.2-3.6GHz, 4 cores with 4 threads)&lt;/li&gt;
  &lt;li&gt;Crucial MX500 SATA 3 SSD&lt;/li&gt;
  &lt;li&gt;16GB RAM&lt;/li&gt;
  &lt;li&gt;Windows 10 WSL 2 Ubuntu 18.04&lt;/li&gt;
  &lt;li&gt;Ubuntu 18.04&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Price: $250 + $70 RAM + $95 SSD ($260 USD) second-hand with new SSD and RAM&lt;/p&gt;

&lt;p&gt;This PC is Dell OptiPlex 9020 that was released mid 2013 – close to my Macbook Pro. Machines like this are great if you are short on budget. They are build to last and cheap. I was lucky and got mine for free from an office giveaway and immediately replaced HDD for SSD. It came with Windows and I started using it for photo editing with Adobe Lightroom and Photoshop at home. Since then I added extra RAM and GPU to make Lightroom snappier (GPU is not included in the price though).&lt;/p&gt;

&lt;p&gt;I will use it as a guinea pig for WSL 2 and bare metal Linux to compare the two. Is it worthy alternative for my trustworthy Macbook? Is WSL ready for serious work? Can we finally develop Rails on Windows?&lt;/p&gt;

&lt;h3 id="macbook-pro-13-2018"&gt;Macbook Pro 13” (2018)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;i7-8559U (2.7-4.5Ghz, 4 cores with 4 threads)&lt;/li&gt;
  &lt;li&gt;16GB RAM&lt;/li&gt;
  &lt;li&gt;macOS 10.14 Mojave&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Price: ~$4 800 ($3 000 USD) new&lt;/p&gt;

&lt;p&gt;Last year’s top of the line Macbook comes with a hefty price tag. I’m not a big fan of the keyboard and touch bar and this nugget belongs to my friend who helped me out with running some of the benchmarks.&lt;/p&gt;

&lt;p&gt;It will be interesting to compare it to the custom-built PC.&lt;/p&gt;

&lt;h2 id="benchmarks"&gt;Benchmarks&lt;/h2&gt;

&lt;p&gt;For each test I ran it 4 times, then discarded the worst value, and averaged the rest. While running the tests I was doing some light web browsing (if there is such a thing) and ran Slack to simulate happy developers’ day.&lt;/p&gt;

&lt;p&gt;These are not your typical synthetic benchmarks but a real world performance. Including hackernews and epilepsy-inducing giphies.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/posts/compiling.png" alt="XKCD compiling classic" height="360" width="413"&gt;&lt;/p&gt;

&lt;h3 id="internal-project"&gt;Internal project&lt;/h3&gt;

&lt;p&gt;This is the most important test. The codebase that I work on day to day is &lt;strong&gt;the&lt;/strong&gt; benchmark. It is a standard Rails monolith with React front-end and the suite is a combination of&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;rubocop,&lt;/li&gt;
  &lt;li&gt;webpacker,&lt;/li&gt;
  &lt;li&gt;rspec (single-threaded),&lt;/li&gt;
  &lt;li&gt;feature specs (headless chrome) and&lt;/li&gt;
  &lt;li&gt;jest.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="/images/posts/2019-10-21-internal-project-benchmark.png" alt="Bare metal Linux is the winner here followed closely by the expensive Macbook" height="371" width="600"&gt;&lt;/p&gt;

&lt;p&gt;Linux is the clear winner here. The 2018 Macbook Pro is ~10% slower than the PC. And WSL 2 is 1.4x slower than running native Linux. What a bummer.&lt;/p&gt;

&lt;p&gt;What surprised me is how good the old Dell with native Linux is. It ran the suite in almost the same time as the custom-built PC from 2019. I don’t have a good explanation for it – I was expecting it to be worse. The CPU is slower (3.6GHz vs 4.6GHz) and the disk is slower (budget SATA 3 SSD vs NVMe).&lt;/p&gt;

&lt;h3 id="ruby"&gt;Ruby&lt;/h3&gt;

&lt;p&gt;Next task is CPU intensive Ruby 2.6.5 compilation.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/posts/2019-10-21-ruby-2.6.5-benchmark.png" alt="Linux PC dominates this test with 2013 Macbook showing its age" height="371" width="600"&gt;&lt;/p&gt;

&lt;p&gt;The 2013 Macbook Pro is showing its age here. The combination of 3.2Ghz with macOS are not cutting it. Again the bare metal Linux dominates and surprisingly the new 2018 Macbook Pro is lagging behind.&lt;/p&gt;

&lt;h3 id="devise"&gt;Devise&lt;/h3&gt;

&lt;p&gt;The popular gem for rails authentication has a small set of unit and integration tests. No parallelisation here either.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/posts/2019-10-21-devise-benchmark.png" alt="WSL 2 on a budget machine is beating $4 800 Macbook Pro" height="371" width="600"&gt;&lt;/p&gt;

&lt;p&gt;Linux PC is up by a mile but the WSL 2 on a budget hardware is beating 2018 Macbook Pro.&lt;/p&gt;

&lt;h3 id="activeadmin"&gt;ActiveAdmin&lt;/h3&gt;

&lt;p&gt;This suite is a good mix of unit, integration, and UI specs. Also, it makes use of some parallelisation so the results might be interesting.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/posts/2019-10-21-active-admin-benchmark.png" alt="First win for 2018 Macbook Pro just by a second from Linux PC" height="371" width="600"&gt;&lt;/p&gt;

&lt;p&gt;First win for macOS on the 2018 Macbook Pro – just by a second ahead of the Linux PC. Solid performance with budget Dell and WSL 2 is acceptable.&lt;/p&gt;

&lt;h3 id="mastodon"&gt;Mastodon&lt;/h3&gt;

&lt;p&gt;Last one is running the social network test suite with a mix of unit, integration, and UI tests. It is a good representation of standard Rails project.&lt;/p&gt;

&lt;p&gt;Unfortunately, it’s missing numbers from the 2018 Macbook Pro but let’s compare the remaining.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/posts/2019-10-21-mastodon-benchmark.png" alt="Linux PC claiming the title again with budget PC lagging by 40s" height="371" width="600"&gt;&lt;/p&gt;

&lt;p&gt;The Linux PC is claiming the title back here and budget PC is lagging behind by more than 40s. There is a performance hit on the WSL 2. With 2013 Macbook Pro you are better off running the suite in your CI pipeline.&lt;/p&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Firstly, all these machines are perfectly capable of developing Ruby and Rails applications. While coding you might be running a spec or two and leave the whole suite to your CI. That makes even a 6 years old laptop acceptable while on the road.&lt;/p&gt;

&lt;p&gt;What surprised me is how good the old Dell with bare metal Linux performed. You can get this machine for so little money and have an excellent PC for work.&lt;/p&gt;

&lt;p&gt;Linux in general is not such a hassle as it used to be. As long as you keep things simple and stick to the vanilla settings. I’m not too demanding and I avoid common pitfalls (high DPI displays, power management, customising UI, etc.) so it works for me. Not having an official OneDrive client and Office sucks though.&lt;/p&gt;

&lt;p&gt;WSL 2, while still in preview, is a decent option and I didn’t run into any issues with it. In combination with the new Windows Terminal and Visual Studio Code it is a joy to work with. It might be the end of Macbook domination in the laptop space for web developers. Still you are getting a performance hit but Windows ecosystem might be worth the trade (or not … depending on your personal preferences).&lt;/p&gt;

</content>
  </entry>
  <entry>
    <id>tag:chodounsky.com,2019-03-23:/2019/03/24/progressive-web-application-as-a-share-option-in-android/</id>
    <title type="html">Progressive web application as a share option in Android</title>
    <published>2019-03-23T21:28:57Z</published>
    <updated>2019-03-23T21:28:57Z</updated>
    <link rel="alternate" href="https://chodounsky.com/2019/03/24/progressive-web-application-as-a-share-option-in-android/" type="text/html"/>
    <content type="html">&lt;p&gt;&lt;a href="/2016/12/23/progressive-web-applications/"&gt;Progressive Web Applications&lt;/a&gt; are becoming more and more powerful and now can be registered as a share intent on Android devices. This means that any Android application can share directly to your web app through the standard native sharing dialog.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/posts/2019-03-24-share-dialog.png" alt="Website in the native share intent dialog" height="640" width="512"&gt;&lt;/p&gt;

&lt;p&gt;It also works the other way around. Your &lt;a href="https://developers.google.com/web/updates/2016/09/navigator-share"&gt;PWA can trigger&lt;/a&gt; the native share dialog and send data to other applications. This brings them one step closer to native apps.&lt;/p&gt;

&lt;p&gt;In this article we will focus on becoming the share target.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Can a progressive web app be registered as a share option in Android?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a long time this wasn’t an option. But with the release of &lt;a href="https://www.chromestatus.com/features/5662315307335680"&gt;Chrome 71&lt;/a&gt; on Android there is an experimental support of the &lt;a href="https://github.com/WICG/web-share-target"&gt;Web Share Target API&lt;/a&gt;. Your PWA can appear in the sharing menu. How cool is that?&lt;/p&gt;

&lt;p&gt;Let’s have a look at what you need to get your website into the Android sharing dialog:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Manifest&lt;/li&gt;
  &lt;li&gt;Service Worker&lt;/li&gt;
  &lt;li&gt;Share target&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id="becoming-progressive"&gt;Becoming progressive&lt;/h2&gt;

&lt;p&gt;The first steps are really about becoming a progressive web application. The share intent will be registered when the user “installs” the app by saving it to the &lt;a href="https://developers.google.com/web/fundamentals/app-install-banners/"&gt;home screen&lt;/a&gt; – either from a popup or manually.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Important note: &lt;strong&gt;Your website needs to be served over HTTPS&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/posts/2019-03-24-add-home.png" alt="Add to home screen popup dialog" height="328" width="600"&gt;&lt;/p&gt;

&lt;p&gt;For that you will need a website manifest and a registered service worker. Firstly, let’s have a look at a simple &lt;code&gt;manifest.json&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
  "short_name": "Sharing Demo",
  "name": "Web Target Sharing Demo",
  "icons": [
    {
      "src": "/android-chrome-192x192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/android-chrome-512x512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": "/",
  "background_color": "#ffffff",
  "display": "standalone",
  "theme_color": "#ffffff"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can read about all the different values and options in more detail &lt;a href="https://developers.google.com/web/fundamentals/web-app-manifest/"&gt;here&lt;/a&gt;. After that, we will need to link to our manifest in our HTML files. So add the following into &lt;code&gt;head&lt;/code&gt; section of your page.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;link rel="manifest" href="/manifest.json"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The Second thing for an app to become a PWA is a registered service worker with a &lt;code&gt;fetch&lt;/code&gt; event. It doesn’t need to do much, it just has to be there. The simplest way to do that is to create &lt;code&gt;service-worker.js&lt;/code&gt; with an empty fetch listener.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;self.addEventListener('fetch', function(event) {});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It won’t do anything but it is everything we need for now. Next, we will register the service worker in our page.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/service-worker.js')
      .then(function(reg){
        console.log("Service worker registered.");
     }).catch(function(err) {
        console.log("Service worker not registered. This happened:", err)
    });
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nice work! It wasn’t too hard and we changed a simple website into a progressive web app. If you are using HTTPS you should be able to see the &lt;em&gt;Add to home screen&lt;/em&gt; popup when you visit your website.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/posts/2019-03-24-add-home-2.png" alt="Add to home screen prompt for progressive web apps" height="155" width="600"&gt;&lt;/p&gt;

&lt;p&gt;You can use Chrome Dev Tools to check on the manifest and see if the app can be installed. Go to &lt;code&gt;Application -&amp;gt; Manifest&lt;/code&gt; or use &lt;a href="https://developers.google.com/web/tools/lighthouse/"&gt;Lighthouse&lt;/a&gt; and see the &lt;em&gt;Installable&lt;/em&gt; section of the report.&lt;/p&gt;

&lt;h2 id="adding-web-share-target"&gt;Adding web share target&lt;/h2&gt;

&lt;p&gt;Converting your website to a progressive web application was actually the hard part. Registering the share intent so your app will appear in the native dialog is super easy.&lt;/p&gt;

&lt;p&gt;You need to add the following lines into your &lt;code&gt;manifest.json&lt;/code&gt; that we created previously.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;  "share_target":
  {
    "action": "/share",
    "params":
    {
      "title": "title",
      "text": "text",
      "url": "url"
    }
  },
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Every time a user will share something through the dialog the request will go to &lt;code&gt;/share&lt;/code&gt; endpoint of your website with query parameters of title, text, and url.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: If you are sharing something from mobile Chrome the URL actually comes through as a text.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now we put everything together and this is the final version of the &lt;code&gt;manifest.json&lt;/code&gt; file with registered &lt;code&gt;share_target&lt;/code&gt; section.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
  "short_name": "Sharing Demo",
  "name": "Web Target Sharing Demo",
  "icons": [
    {
      "src": "/android-chrome-192x192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/android-chrome-512x512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "share_target":
  {
    "action": "/share",
    "params":
    {
      "title": "title",
      "text": "text",
      "url": "url"
    }
  },
  "start_url": "/",
  "background_color": "#ffffff",
  "display": "standalone",
  "theme_color": "#ffffff"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After that, you can use any method of reading the query parameters that suits your needs. Either server-side like this with Ruby on Rails:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def share
  @url = params[:url]
  @title = params[:title]
  @text = params[:text]
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;or client-side with a bit of good old javascript:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var parsedUrl = new URL(window.location.toString());
console.log('Title shared: ' + parsedUrl.searchParams.get('name'));
console.log('Text shared: ' + parsedUrl.searchParams.get('description'));
console.log('URL shared: ' + parsedUrl.searchParams.get('link'));
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I’m a big fan of progressive web apps and the web share target makes the integration with your mobile workflow much smoother. It comes with a few caveats of being experimental so the API will most likely change or it could totally disappear. Also, keep in mind that (at the moment) it’s supported only on Android devices and your Apple users won’t benefit from it
. The proposal takes iOS into account though so maybe some time in the future. I’ve been waiting for this feature for a while and I’m super-excited that it made it to the Android community.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>tag:chodounsky.com,2018-03-13:/2018/03/13/tips-for-getting-a-programming-job-abroad/</id>
    <title type="html">Tips for getting a programming job abroad</title>
    <published>2018-03-13T06:42:38Z</published>
    <updated>2018-03-13T06:42:38Z</updated>
    <link rel="alternate" href="https://chodounsky.com/2018/03/13/tips-for-getting-a-programming-job-abroad/" type="text/html"/>
    <content type="html">&lt;p&gt;A few years ago I moved from the Czech Republic to New Zealand. I didn’t have a network, a job offer, or a scheduled interview.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/posts/2018-03-13-tips-for-getting-a-programming-job-abroad.jpg" alt="On the way to the most northern part of New Zealand --- Cape Reinga" height="640" width="960"&gt;&lt;/p&gt;

&lt;p&gt;There are lots of benefits of moving to a different country. You are opening yourself to different ideas, mindset, and experience. You can learn the language, better understand a foreign culture, and adopt new ways of doing things. It is a scary step but one worth doing.&lt;/p&gt;

&lt;p&gt;I had never quit a job without another offer. And now I was moving across the globe with anything in my hands. In the end, I managed to find a great job. Sorted out a residency. And have an awesome career. I’d like to share a few tips that will make it easier if you decide to do the same.&lt;/p&gt;

&lt;h2 id="research"&gt;Research&lt;/h2&gt;

&lt;p&gt;It is important to do your own research. Firstly, you need to figure out how much the cost of living in your destination is and how much money you might earn.&lt;/p&gt;

&lt;p&gt;I found &lt;a href="https://www.numbeo.com/cost-of-living/in/Wellington"&gt;Numbeo&lt;/a&gt; to be a great source of information. You can compare the numbers in your current location and adjust them if they are not that accurate. Remember that you will learn by trial and error so the first few months might be more costly than you planned.&lt;/p&gt;

&lt;p&gt;Also, searching for &lt;em&gt;“Cost of living in [destination]”&lt;/em&gt; might lead you to other useful resources from expats, travel bloggers, and even &lt;a href="https://www.newzealandnow.govt.nz/living-in-nz/money-tax/comparable-living-costs"&gt;governments&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now it is time to figure out how much you might earn. There are heaps of global sources on this topic like &lt;a href="https://www.payscale.com/research/NZ/Job=Senior_Software_Engineer/Salary/e22c4d28/Wellington"&gt;PayScale&lt;/a&gt; or &lt;a href="https://www.glassdoor.com/Salaries/wellington-software-developer-salary-SRCH_IL.0,10_IM1179_KO11,29.htm"&gt;Glassdoor&lt;/a&gt; but the best place to start is a local job board. Some posts will have a salary range and you will get the feel on how many jobs are out there. For New Zealand there are two major boards: &lt;a href="https://seek.co.nz"&gt;Seek&lt;/a&gt; and &lt;a href="https://www.trademe.co.nz/jobs"&gt;TradeMe&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another great place to get information about wages is a report from local recruiting agencies. A few of them publish salary reports for different IT positions and technologies.&lt;/p&gt;

&lt;p&gt;Money is important but don’t forget to take into account other factors that are dear to you. It could be pollution, commute time, work-life balance, ocean, mountains, sun, health care, active community, and many others. For that, I also recommend Numbeo’s indexes: &lt;a href="https://www.numbeo.com/quality-of-life/rankings.jsp"&gt;quality of life&lt;/a&gt;, &lt;a href="https://www.numbeo.com/pollution/rankings.jsp"&gt;pollution&lt;/a&gt;, and a bit more time spent on Google. Seriously, I didn’t know that Wellington is one of the windiest cities in the world.&lt;/p&gt;

&lt;p&gt;While you are scanning the job boards you will get an idea about the popularity of your tech stack. More opportunities equal to less stressful transition. And remember, your first position doesn’t have to be a dream job. A stable job that helps you with your visa situation and a good work-life balance can be a great choice for the first few years while you are settling in the new country. On the other hand, if you are highly specialized (e.g. research in computer graphics algorithms) and there would be a company that could use your skill (Wellington is a home of movie effects) it could be easier.&lt;/p&gt;

&lt;p&gt;When you want to work in another country you will need a work visa. If you are moving from one EU country to another it is very simple thanks to the Schengen area. Similarly, New Zealand has a &lt;a href="http://newzealand.embassy.gov.au/wltn/Visas_and_Immigration.html"&gt;pact&lt;/a&gt; with Australia and I bet other countries do as well.&lt;/p&gt;

&lt;p&gt;A few countries (&lt;a href="https://www.homeaffairs.gov.au/Trav/Visa-1/462-"&gt;Australia&lt;/a&gt;, &lt;a href="https://www.immigration.govt.nz/new-zealand-visas/options/work/thinking-about-coming-to-new-zealand-to-work/working-holiday-visa"&gt;New Zealand&lt;/a&gt;, &lt;a href="https://www.canada.ca/en/immigration-refugees-citizenship/services/work-canada/iec.html"&gt;Canada&lt;/a&gt;, and others) have a working holiday program which might be a good starting option if you match the entering criteria.&lt;/p&gt;

&lt;p&gt;Getting the visa can be the biggest hurdle but there is a shortage of good programmers all around the world so companies might go above and beyond to get your skills. Keep in mind that migrants are still a big risk for the company and getting them onboard is time-consuming and not that cheap.&lt;/p&gt;

&lt;h2 id="building-a-network"&gt;Building a network&lt;/h2&gt;

&lt;p&gt;To find a great job it helps to have a network. The best positions could come from your friends and colleagues you worked and built great relationships with. But how can you get around that when you have no connections and starting from the ground?&lt;/p&gt;

&lt;p&gt;There are a few things that you can do before you arrive at the new country. It won’t be as effective but it will do the trick.&lt;/p&gt;

&lt;p&gt;Start making connections by cold emailing a few people from local communities. Find out if there is a meetup group for your stack (an example could be the &lt;a href="https://www.meetup.com/WelliDotNet/"&gt;WelliDotNet&lt;/a&gt; group in Wellington). Another place where programmers hang out “locally” could be Slack channels, IRC, or Facebook groups. Do a bit of a legwork and find those places. If all fails there is always LinkedIn. People are generally pretty helpful even to a complete stranger. A polite and informed stranger though.&lt;/p&gt;

&lt;p&gt;Choose people that seem to be active in the community—explain your current situation and show them you did some research and put an effort into the message. Ask a few and easy to answer questions and start building that relationship. Best people to contact could be past presenters, organizers, administrators, or just the ones who seems to be answering others’ questions. Get in touch with at least a few different people to “diversify” so you limit a chance of reaching out people inside a bubble.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hi Jane,&lt;/p&gt;

  &lt;p&gt;my name is John and I’m a .NET programmer from Prague. I’ve been programming for over 5 years and mostly in bigger companies in healthcare. I’m looking to move to New Zealand and was wondering if I could ask you a few questions.&lt;/p&gt;

  &lt;p&gt;I love working with diverse people that care, on a product that helps. Bigger companies might be a better choice for me as I’d like to have some stability to get my family settled first.&lt;/p&gt;

  &lt;p&gt;It seems that there are a few big .NET companies in Wellington. Xero, TradeMe, and Datacom. Are those nice places to work? Would you recommend any others? Should I stay away from some of the local companies?&lt;/p&gt;

  &lt;p&gt;There is also lots of opportunities in Auckland, but people seem to prefer Wellington. Would you recommend Wellington over Auckland as well?&lt;/p&gt;

  &lt;p&gt;Thank you for your time,&lt;br&gt;
John&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you moved it’s nice to get in touch with those that helped and buy them a coffee or a beer. A simple “thank you” will also do if you run into them at one of the meetups.&lt;/p&gt;

&lt;h2 id="applying"&gt;Applying&lt;/h2&gt;

&lt;p&gt;From my experience companies and recruiters won’t take you seriously until you move (unless your destination is just a short flight away). You can start sending CVs and cover letters but don’t feel bad if you are not getting any response. It will change once you arrive.&lt;/p&gt;

&lt;p&gt;Always mention your visa situation in your CV and note it in your cover letter as well. If you have a work visa it will be to your advantage and if not at least you are not putting the company on the spot with an unpleasant surprise (some of them can’t afford to sort the visa out for you).&lt;/p&gt;

&lt;p&gt;When you are new to the country you might fall into a certain bias and getting your first job will be harder than the next one. Employers might worry that you don’t share the same work culture and ethics, and will have a language barrier. Because of that, you might not get jobs that you are a perfect fit. If that happens to you don’t worry too much about it and don’t get discouraged.&lt;/p&gt;

&lt;p&gt;Another point to consider is mentioning your family (or partner) situation. That’s a fair point because a common thing I’ve seen is that programmers (who get the job) arrive with their partners that can’t find any job. The partner becomes unhappy in a few months, and want to leave back to their country. The programmer will usually follow them and the employer loses a member of the team they invested in and need to start hiring again. To mitigate the risk show commitment to staying in the country and if you are not alone mention what’s the plan for your significant other. You don’t have to be too specific—just the fact that you thought about it shows a lot.&lt;/p&gt;

&lt;p&gt;Lastly, it is easier to follow up with the leads you gathered through remote networking than applying to recruiters and job ads. But don’t frown upon any of those and use all the channels that are available.&lt;/p&gt;

&lt;p&gt;It won’t be easy but if you persist you will find a good place to work for.&lt;/p&gt;

&lt;h2 id="tips"&gt;Tips&lt;/h2&gt;

&lt;p&gt;I can’t stress enough the importance of having enough money in your bank account. At least a few months worth of rent and food (and possibly a return ticket back home) will help your confidence and greatly reduce the already stressful experience. It will allow you to take your time to interview for different jobs, find a place to live that you will enjoy, and helps to settle in. You want to take enough time and don’t rush anything.&lt;/p&gt;

&lt;p&gt;Make sure you have a plan for your family and your significant other. If they are following you and they won’t have a job it will be very hard for them to be happy. Remember that they won’t have a network of longtime friends and family to lean on as they would in your original country.&lt;/p&gt;

&lt;p&gt;If you want this experience to be a success and not a stressful adventure do your research upfront and be prepared. And even if you do your homework there will be lots of surprises.&lt;/p&gt;

&lt;p&gt;Lastly, when you have multiple offers to choose from you should think about a few additional things then you’d normally do. If you are on a temporary work visa is the employer willing to help you with getting the permanent one? You might be better off joining a bigger team to expand your professional network than a small startup with just a handful of people. Also, don’t underestimate the social aspect of the job—it might be the place where some of your work relationships transform into your first local friendships. Maybe in a diverse environment that supports immigration you will find people in similar situation and that will bring you together.&lt;/p&gt;

&lt;p&gt;Working in a different country is hard, stressful, but in the end, it is worth it. Good luck!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>tag:chodounsky.com,2017-05-01:/2017/05/03/garbage-collection-in-c-sharp/</id>
    <title type="html">Garbage collection in C#</title>
    <published>2017-05-01T21:06:59Z</published>
    <updated>2017-05-01T21:06:59Z</updated>
    <link rel="alternate" href="https://chodounsky.com/2017/05/03/garbage-collection-in-c-sharp/" type="text/html"/>
    <content type="html">&lt;p&gt;When you are programming, no matter the task on hand, you are manipulating some data. These are stored in basic types and objects and they live inside computer memory. Eventually, the memory fills up and you need to make more room for new data and discard the old one.&lt;/p&gt;

&lt;p&gt;You can do it either by hand, like C and C++ programmers (used to) do, or use a languages with a mechanism that does it for you. In C# we are fortunate enough to have a &lt;a href="https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)"&gt;garbage collector&lt;/a&gt; that takes care of our memory.&lt;/p&gt;

&lt;h2 id="basic-concepts"&gt;Basic concepts&lt;/h2&gt;

&lt;p&gt;In short, garbage collector tries to find objects that are no longer in use by the program and delete them from memory.&lt;/p&gt;

&lt;p&gt;The first garbage collected language was &lt;a href="https://en.wikipedia.org/wiki/Lisp_(programming_language)"&gt;LISP&lt;/a&gt; written by &lt;a href="https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)"&gt;John McCarthy&lt;/a&gt; – one of the founders of artificial intelligence – around 1959. So the concept has been around for a while.&lt;/p&gt;

&lt;p&gt;When you have a garbage collector in place you are making a trade off of safe memory allocations and programmer’s productivity for performance overhead. Garbage collected language will never be suitable for real-time critical applications like air traffic control, but you can get solid performance out of it – I’ve seen successful high frequency trading platforms written in C#.&lt;/p&gt;

&lt;p&gt;Garbage collection is not only responsible for cleaning the memory and compacting it, but also allocating new objects.&lt;/p&gt;

&lt;p&gt;.NET and CLR are making use of tracing garbage collector. That means that on every collection the collector figures out whether the object is used by tracing every object from stack roots, GC handles and static data.&lt;/p&gt;

&lt;p&gt;Every object that could be traced is marked as live and at the end of tracing the ones without the mark are removed (swept) from the memory and it gets compacted. This is called a simple &lt;strong&gt;mark and sweep algorithm&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/posts/Animation_of_the_Naive_Mark_and_Sweep_Garbage_Collector_Algorithm.gif" alt="Naive mark and sweep in action (source Wikipedia)" height="321" width="420"&gt;&lt;/p&gt;

&lt;p&gt;It gets a bit &lt;a href="https://en.wikipedia.org/wiki/Tracing_garbage_collection"&gt;more complex&lt;/a&gt; than that but this is enough for having a simple mental model of what’s happening under the hood.&lt;/p&gt;

&lt;p&gt;If the garbage collector halts the program when running we call this event &lt;strong&gt;stop the world&lt;/strong&gt;. That guarantees that no new objects are allocated during collection, but it also means that there will be a delay in our program. To minimise that disruption &lt;strong&gt;incremental&lt;/strong&gt; and &lt;strong&gt;concurrent&lt;/strong&gt; garbage collectors has been designed.&lt;/p&gt;

&lt;p&gt;Incremental collection makes the pauses shorter and works in small increments and concurrent doesn’t stop the program at all. For that you are paying with the overall longer collection time and the use of more CPU and memory resources than traditional stop the world.&lt;/p&gt;

&lt;h2 id="generational-collection"&gt;Generational collection&lt;/h2&gt;

&lt;p&gt;In 1984 David Ungar came up with a &lt;a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.122.4295&amp;amp;rep=rep1&amp;amp;type=pdf"&gt;generational hypothesis&lt;/a&gt; that layed the foundations of GC that are in use these days.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Young objects die young. Therefore reclamation algorithm should not waste time on old objects.&lt;br&gt;
Copying survivors is cheaper than scanning corpses.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This hypothesis gave birth of the generational garbage collectors – first used in Smalltalk and used in all modern platforms today – C# is not an exception.&lt;/p&gt;

&lt;p&gt;The memory is divided into several generations based on the age of objects. The collection happens in the youngest generation and the surviving objects are promoted to the generation above.&lt;/p&gt;

&lt;p&gt;In C# we have 3 generations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Generation 0&lt;/strong&gt; is the youngest generation with short-lived objects in which the collection happens the most often. New objects are placed here with the exception of large objects that go straight to the Generation 2.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Generation 1&lt;/strong&gt; serves as a buffer between the short-lived and long-lived objects.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Generation 2&lt;/strong&gt; is the place for the long-lived objects and it gets collected less often.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When there is a collection over Generation 2 we call it &lt;em&gt;full garbage collection&lt;/em&gt; as it goes through all objects in managed memory.&lt;/p&gt;

&lt;p&gt;To keep the balance between generations most modern platforms are using a hybrid approach of generational cycles (minor cycle) and some variation of full mark and sweep (major cycle).&lt;/p&gt;

&lt;p&gt;The CLR automatically adjusts itself between not letting the used memory get too big and not letting the garbage collection take too much time by setting thresholds for new object allocation.&lt;/p&gt;

&lt;p&gt;The collection gets triggered when&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;memory is low,&lt;/li&gt;
  &lt;li&gt;threshold is exceeded or&lt;/li&gt;
  &lt;li&gt;
&lt;code&gt;GC.Collect&lt;/code&gt; is called manually.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the collection is triggered every thread is suspended, so it can’t allocate new memory, and the GC thread goes into action.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/posts/IC393001.png" alt="When a thread triggers Garbage Collection (source MSDN)" height="176" width="564"&gt;&lt;/p&gt;

&lt;p&gt;As you already know the collector uses static data to know whether the object is alive or not. As the static fields are kept in memory forever (until the AppDomain gets unloaded) this might lead to a common memory leak when it refers to a collection and you keep adding objects to the collection. The static field keeps the collection alive and all the objects inside alive as well. Be careful about that and possibly avoid the usage of static fields if you can.&lt;/p&gt;

&lt;h2 id="characteristics"&gt;Characteristics&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e"&gt;Modern garbage collectors&lt;/a&gt; have different characteristics and as with many things in programming – different trade offs. Based on the application requirements they could be tuned towards less interruptions and better user experience or maybe you want to run a fast server-side application that scales.&lt;/p&gt;

&lt;p&gt;What characterizes a Garbage Collector?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Program throughput&lt;/strong&gt;: CPU time spent collecting vs CPU time doing useful work.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;GC throughput&lt;/strong&gt;: amount of data the collector can clear per CPU time.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Heap overhead&lt;/strong&gt;: additional memory the collector produced during collection.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Pause times&lt;/strong&gt;: time needed for collection.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Pause frequency&lt;/strong&gt;: how often does garbage collection happen?&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Pause distribution&lt;/strong&gt;: short pauses vs long pauses, and how consistent the pauses are.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Allocation performance&lt;/strong&gt;: how much time does the new object allocation take? Is it predictable?&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Concurrency&lt;/strong&gt;: does the collector make use of multicore CPUs?&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Scaling&lt;/strong&gt;: can your GC handle larger heaps?&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Warmup time&lt;/strong&gt;: can a collector adjust to the characteristics of your application? How long does it take to adjust?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="configuration"&gt;Configuration&lt;/h2&gt;

&lt;p&gt;You can influence the type and behavior of your application by setting garbage collection to a different type.&lt;/p&gt;

&lt;p&gt;There are two major types – workstation and server. They could be configured by setting &lt;code&gt;&amp;lt;gcServer enabled="true|false" /&amp;gt;&lt;/code&gt; element in your application configuration.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;configuration&amp;gt;
   &amp;lt;runtime&amp;gt;
       &amp;lt;gcServer enabled="true"/&amp;gt;
   &amp;lt;/runtime&amp;gt;
&amp;lt;/configuration&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The workstation setting is tuned for client-side applications. Low latency is preferred as you don’t want your forms in WPF application become unresponsive during a long pause caused by garbage collection. In this mode it favours user experience and less CPU usage.&lt;/p&gt;

&lt;p&gt;On the other hand, the server configuration is designed for high throughput and scalability of server-side applications and it uses multiple dedicated threads for garbage collection with the highest priority. It is faster than workstation but that also comes with more CPU and memory usage. This configuration assumes that no other applications are running on the server and prioritise the GC threads accordingly. Also, it splits the managed heap into sections based on the number of CPUs. There is one special GC thread per CPU that takes care of the collection of its section. You can run this mode only on a computer with multiple CPUs.&lt;/p&gt;

&lt;p&gt;By default, the workstation GC mode is active.&lt;/p&gt;

&lt;p&gt;Another option you can choose is to use concurrent collection by using the &lt;code&gt;&amp;lt;gcConcurrent enabled="true|false" /&amp;gt;&lt;/code&gt;. It will give you more responsive application with less pause frequency and time.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;configuration&amp;gt;
   &amp;lt;runtime&amp;gt;
       &amp;lt;gcConcurrent enabled="false"/&amp;gt;
   &amp;lt;/runtime&amp;gt;
&amp;lt;/configuration&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In concurrent mode, there will be an extra background thread that marks the objects alive when your application is executing its code. When it comes to collection, it will be faster as the set of dead objects has already been built.&lt;/p&gt;

&lt;p&gt;The concurrency only affects the generation 2. Generation 0 and 1 will always be non-concurrent as they both finish fast and are not worth the extra overhead. When concurrent option is set, a dedicated thread will be used for collection.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/posts/IC393003.png" alt="A dedicated thread is used for collection in concurrent mode (source MSDN)" height="230" width="562"&gt;&lt;/p&gt;

&lt;p&gt;To improve performance of several processes running side by side, you can try using workstation garbage collection with the concurrent collection disabled. This will lead to less context switching and therefore better performance.&lt;/p&gt;

&lt;h2 id="performance"&gt;Performance&lt;/h2&gt;

&lt;p&gt;The memory is divided into small object heap and large object heap. Large objects are the ones above 85 000 bytes and they go straight into the Generation 2.&lt;/p&gt;

&lt;p&gt;In general, fewer objects on the managed heap is less work for garbage collector. That is applied especially to the large object heap that gets collected less often.&lt;/p&gt;

&lt;p&gt;If you feel like GC causes you troubles there are various tools that you can use to &lt;a href="https://msdn.microsoft.com/en-us/library/ee851764(v=vs.110).aspx#performance_analysis_tools"&gt;debug it&lt;/a&gt; – memory performance counter, WinDbg or tracing ETW events.&lt;/p&gt;

&lt;p&gt;Triggering collection manually with &lt;code&gt;GC.Collect&lt;/code&gt; is usually more counterproductive than beneficial unless you really know what you are doing. It might help in certain situations when you want to release a lot of memory at once (e.g. a large dialog is closed and won’t be used again) – but unless you have diagnosed a memory problem, don’t go there.&lt;/p&gt;

&lt;p&gt;If you are calling &lt;code&gt;GC.Collect&lt;/code&gt; manually too frequently you will certainly see a decrease of performance in your application.&lt;/p&gt;

&lt;p&gt;When you get into situation where the latency of your application is critical – like in the middle of the high frequency trading decision loop – you can set a &lt;em&gt;&lt;a href="https://msdn.microsoft.com/en-us/library/bb384202(v=vs.110).aspx"&gt;low latency mode&lt;/a&gt;&lt;/em&gt;. This will put the GC into a low intrusive mode when it will become very conservative.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;GCLatencyMode oldMode = GCSettings.LatencyMode;
System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions();
try
{
    GCSettings.LatencyMode = GCLatencyMode.LowLatency;
    ...
}
finally
{
    GCSettings.LatencyMode = oldMode;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The collection of Generation 2 will be paused for the time in low latency mode. That leads to shorter and less frequent pause times and therefore decreased program latency. But that comes with the price that you might run out of memory if you are not careful. The best guideline is to keep those low latency sections as short as possible and don’t allocate too many new objects, especially on the large heap. In this case you might want to call &lt;code&gt;GC.Collect&lt;/code&gt; where appropriate to force the Generation 2 collection.&lt;/p&gt;

&lt;p&gt;Don’t forget that the latency mode is process wide, so if you are running multiple threads it will be applied to all of them.&lt;/p&gt;

&lt;h2 id="managed-and-unmanaged-resources"&gt;Managed and unmanaged resources&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://en.wikipedia.org/wiki/Common_Language_Runtime"&gt;&lt;abbr title="Common Language Runtime"&gt;CLR&lt;/abbr&gt;&lt;/a&gt; all the code that is written is managed and it will be garbage collected. C# gets compiled into &lt;abbr title="Common Intermediate Language"&gt;CIL&lt;/abbr&gt; and runs on top of CLR so all your C# code will be garbage collected.&lt;/p&gt;

&lt;p&gt;That sounds easy enough until you need to use unmanaged resources.&lt;/p&gt;

&lt;p&gt;And you will use them on almost daily basis as these are database connections, COM interops, network and file streams and many others. Unmanaged resources are not garbage collected and you need to free them from your memory by hand.&lt;/p&gt;

&lt;p&gt;If you write a type that uses an unamanged resource you should&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;implement the &lt;a href="https://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx"&gt;dispose pattern&lt;/a&gt; and&lt;/li&gt;
  &lt;li&gt;provide a mechanism to free the resource in case consumer forgets to call Dispose.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The basic implementation of a dispose pattern with the use of SafeHandle could look like&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public class DisposableResourceHolder : IDisposable
{
    private SafeHandle resource;

    public DisposableResourceHolder()
    {
        this.resource = ...
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (resource != null) resource.Dispose();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To free the resource you can use &lt;code&gt;Object.Finalize&lt;/code&gt; or &lt;code&gt;SafeHandles&lt;/code&gt;. For more details check out &lt;a href="https://msdn.microsoft.com/en-us/library/498928w2(v=vs.110).aspx"&gt;Cleaning up Unmanaged Resources&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a consumer of a type with unmanaged resource always use the &lt;code&gt;using&lt;/code&gt; pattern.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;using (var s = new StreamReader("file.txt"))
{
  ...
  s.Close();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;How can you tell the object needs to be disposed? It implements the &lt;code&gt;IDisposable&lt;/code&gt; interface. Some static analysis tools can help you notice the cases where you might have forgotten to dispose your objects.&lt;/p&gt;

&lt;h2 id="what-about-other-languages"&gt;What about other languages?&lt;/h2&gt;

&lt;p&gt;The first versions of &lt;strong&gt;Ruby&lt;/strong&gt; used a simple mark and sweep technique which isn’t the best performer but it was simple for the authors of C-extensions to write native extensions. That simplicity was the key to Ruby’s growth in the beginning and took it where it is these days. Generational collection was introduced in Ruby 2.1 (current version is 2.4) to improve the throughput of programs – quite late in terms of language maturity. After that Ruby 2.2 introduced &lt;a href="https://blog.heroku.com/incremental-gc"&gt;incremental marking&lt;/a&gt; which addresses long pause times by running the GC in short increments.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/posts/1488278432.png" alt="Stop the world vs. incremental marking (source Heroku)" height="227" width="1153"&gt;&lt;/p&gt;

&lt;p&gt;Programmers in &lt;strong&gt;C++&lt;/strong&gt; don’t have any garbage collection and they need to do memory management by hand. There are techniques to make that easier – &lt;a href="http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/smart_ptr.htm"&gt;smart pointer&lt;/a&gt; are now part of the C++ 11 standard and are a tool that automatically deletes memory from the heap. Smart pointer can be implemented with &lt;a href="https://en.wikipedia.org/wiki/Reference_counting"&gt;reference counting&lt;/a&gt; that ensures that the object is deleted as soon as it is no longer needed – as opposed to tracing garbage collection when the unused object waits for the next cycle of collection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Java&lt;/strong&gt; is very similar to C# and uses &lt;a href="http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html"&gt;tracing generational garbage collection&lt;/a&gt; – the heap is structured to a young generation (eden, S0 and S1), old generation and permanent generation. GC uses minor and major cycles to clean the generations and promote objects from one to the other. Both the collections of young and old generations are the “stop the world” event so all the threads are suspended until they finish. There are different types of collectors on JVM – serial, parallel, concurrent mark and sweep and its replacement G1 in Java 7.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt; doesn’t use tracing garbage collection but a &lt;a href="https://www.quora.com/How-does-garbage-collection-in-Python-work"&gt;reference counting with periodical cycle detection&lt;/a&gt; to solve the case of two dead objects pointing to each other. But as opposed to classic reference counting it combines it with the generational approach and uses the reference count instead of the mark and sweep algorithm. It doesn’t deal with memory fragmentation but tries to avoid it with allocating objects on different pools of memory.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Javascript&lt;/strong&gt; there isn’t a unified approach to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management"&gt;garbage collection&lt;/a&gt;. It is in hands of browser vendors and they do it differently – Internet 6 and 7 used reference counting garbage collectors for DOM objects. From 2012 almost all modern browsers are shipped with tracing mark and sweep algorithms with some extra improvements – generations, incremental collection, concurrency and parallelism.&lt;/p&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;C# and languages on top of CLR are garbage collected. They use generational garbage collection with 3 generations and an advanced tracing mark and sweep algorithm to figure out which objects are still alive.&lt;/p&gt;

&lt;p&gt;You can change the characteristics of the collector by changing to a server or workstation mode and setting up a concurrency mode.&lt;/p&gt;

&lt;p&gt;Most of the times triggering garbage collection manually is not a good idea unless you really know what you are doing. And in cases where latency is important you can use latency mode to increase your program throughput and eliminate pauses.&lt;/p&gt;

&lt;p&gt;Lastly, there are unmanaged resources like file streams, network, database connections that you need to take a special care of. Most of the times you’ll be implementing and using the IDisposable pattern so you don’t cause any memory leaks in your application.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;&lt;em&gt;If you liked this article you might enjoy &lt;a href="http://csharpdigest.net?utm_source=chodounsky.net&amp;amp;utm_medium=blog&amp;amp;utm_campaign=garbage+collection"&gt;C# Digest&lt;/a&gt; newsletter with 5 links from the .NET community that I put together every week.&lt;/em&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;h2 id="resources"&gt;Resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://msdn.microsoft.com/en-us/library/0xy59wtx(v=vs.110).aspx"&gt;Garbage collection on MSDN&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)"&gt;Garbage collection on Wikipedia&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Tracing_garbage_collection"&gt;Tracing garbage collection&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e#.y5kgl8qvc"&gt;Mike Hearn on Modern garbage collection&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.122.4295&amp;amp;rep=rep1&amp;amp;type=pdf"&gt;David Ungar on Generation Scavenging&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="comments"&gt;Comments&lt;/h2&gt;

&lt;p&gt;Justin Self: Nice Post. Minor note: Large objects (over 85,000 bytes) get collected during a Gen 2 collection but are not really part of the Gen 2. They are part of the Large Object Heap.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>tag:chodounsky.com,2017-01-19:/2017/01/20/automate-your-macos-development-machine-setup/</id>
    <title type="html">Automate your macOS development machine setup</title>
    <published>2017-01-19T20:30:21Z</published>
    <updated>2017-01-19T20:30:21Z</updated>
    <link rel="alternate" href="https://chodounsky.com/2017/01/20/automate-your-macos-development-machine-setup/" type="text/html"/>
    <content type="html">&lt;p&gt;My laptop was running a bit slow and there was an OS upgrade that I’ve been postponing for quite some time. So, on one rainy Friday afternoon, I decided to get my hands dirty and do a clean install of macOS Sierra. And why not automate it so I don’t have to think about it next time?&lt;/p&gt;

&lt;p&gt;One of the benefits is that my team mates can use chunks of the configuration that are relevant to our work and use it for themselves.&lt;/p&gt;

&lt;p&gt;Let’s have a look at the tools that we will use.&lt;/p&gt;

&lt;h2 id="dotfiles"&gt;Dotfiles&lt;/h2&gt;

&lt;p&gt;It’s a bunch of &lt;a href="https://dotfiles.github.io/"&gt;configuration files&lt;/a&gt; and directories that are in your home directory. For example vim uses &lt;code&gt;.vimrc&lt;/code&gt; or a git uses &lt;code&gt;.gitconfig&lt;/code&gt;. You want to bring them with you from your old machine.&lt;/p&gt;

&lt;p&gt;Some people store them as a git repository but I just chuck them on Dropbox. Make sure that you are &lt;strong&gt;not revealing any sensitive files&lt;/strong&gt; like your private SSH keys or infrastructure setup.&lt;/p&gt;

&lt;p&gt;When you setup your new machine you create symlinks to the ones that you like to use. It can get &lt;a href="http://codyreichert.github.io/blog/2015/07/07/managing-your-dotfiles-with-gnu-stow/"&gt;pretty fancy&lt;/a&gt; or you can keep it simple with &lt;code&gt;ln -s&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id="brew"&gt;Brew&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://brew.sh/"&gt;Homebrew&lt;/a&gt; is a package manager for macOS. And you can create your own &lt;a href="https://github.com/Homebrew/homebrew-bundle"&gt;bundles&lt;/a&gt; to set up whole environment.&lt;/p&gt;

&lt;p&gt;Apart from that there is an extension for installing desktop applications — &lt;a href="https://caskroom.github.io/"&gt;cask&lt;/a&gt;. You can search for the recipes with the following commands.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;brew search vim
brew cask search google-chrome
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;When you find the mix of applications you bundle them together into a &lt;code&gt;Brewfile&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cask_args appdir: '/Applications'

tap 'homebrew/bundle'
tap 'caskroom/cask'
tap 'homebrew/services'

brew 'vim'
brew 'tmux'
brew 'git'
brew 'ruby-build'
brew 'rbenv'

cask 'google-chrome'
cask 'vlc'
cask 'dropbox'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I like to keep my &lt;code&gt;Brewfile&lt;/code&gt; in my Dropbox folder with the rest of the dotfiles. Now with the file you can run &lt;code&gt;brew bundle&lt;/code&gt; and it installs everything for you. Easy.&lt;/p&gt;

&lt;h2 id="everything-together"&gt;Everything together&lt;/h2&gt;

&lt;p&gt;We have our configuration with dotfiles and we can install all the necessary applications with brew and cask. How about putting everything into one script that we can run and leave the computer do its thing?&lt;/p&gt;

&lt;p&gt;I created an &lt;code&gt;install.sh&lt;/code&gt; file that I keep with the rest of the files.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#!/bin/bash

xcode-select --install

# dotfiles
ln -s ~/Dropbox/dotfiles/.bash_profile ~/.bash_profile
ln -s ~/Dropbox/dotfiles/.gitconfig ~/.gitconfig
ln -s ~/Dropbox/dotfiles/.tmux.conf ~/.tmux.conf
ln -s ~/Dropbox/dotfiles/.vimrc ~/.vimrc

# brew stuff
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
cd ~/Dropbox/dotfiles
brew bundle
cd ~

git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim
git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm

# ruby
rbenv install 2.2.5
rbenv global 2.2.5

gem install bundler

echo "******************** Done ********************"
echo "Don't forget to configure SSH properly with key and config"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Firstly, it installs XCode command line tools — if you need to install the full version you might need to do that through AppStore. I haven’t found any way around that yet.&lt;/p&gt;

&lt;p&gt;After installing XCode the script takes care of the dotfiles and links them into your home directory. And when those are sorted it starts the brew magic.&lt;/p&gt;

&lt;p&gt;There is a few custom commands afterward — installing plugin managers for tmux and vim and installing ruby with rbenv. You can also use terminal to customize macOS behaviour or do other things that you would normally do by hand.&lt;/p&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;And with that a fresh OS install is not a pain anymore. After finishing the installation and encrypting your disk you copy your backed up Dropbox folder into your home directory, run&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;. ~/Dropbox/dotfiles/install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and you can go for a surf, come back and have your computer ready. Unless you need to compile Qt with Webkit. Then you might go for two surf sessions at least.&lt;/p&gt;

&lt;h2 id="comments"&gt;Comments&lt;/h2&gt;

&lt;p&gt;Victor Maslov aka Nakilon: My relevant &lt;a href="https://gist.github.com/Nakilon/7db114214d9019824543684ed4074891"&gt;gist&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gabriel Filipe Gizotti: &lt;a href="https://github.com/freshshell/fresh"&gt;fresh&lt;/a&gt; helped me a lot&lt;/p&gt;

&lt;p&gt;ege: Brewfile also let you add apps from Mac App Store thanks to github.com/mas-cli/mas. But doesn’t work with 2FA-enabled account yet. I hope devs find a solution.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;brew 'mas' / brew install mas
mas '1Password'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Phil Pirozhkov: I find it really hard maintaining the up to date list of formulae by hand and came up with the following:&lt;/p&gt;

&lt;p&gt;Show installed formulae that are not dependencies of another installed formula:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;brew leaves &amp;gt;! .formulae
brew cask list &amp;gt;! .casks
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Install:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;brew install $(&amp;lt; .formulae )
brew tap caskroom/cask
brew cask
brew cask install $(&amp;lt; .casks)
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <id>tag:chodounsky.com,2016-12-22:/2016/12/23/progressive-web-applications/</id>
    <title type="html">Progressive Web Applications</title>
    <published>2016-12-22T19:52:09Z</published>
    <updated>2016-12-22T19:52:09Z</updated>
    <link rel="alternate" href="https://chodounsky.com/2016/12/23/progressive-web-applications/" type="text/html"/>
    <content type="html">&lt;p&gt;Every time there is a chance to build a mobile application with javascript, web developers are like &lt;em&gt;“yeah, this is the way; the future of building mobile apps”&lt;/em&gt; and jump on the bandwagon of the saucy java intent avoidance.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;[WHATEVER JS FRAMEWORK] will change the way we build mobile apps
&lt;small&gt;&lt;cite&gt;Every web developer ever&lt;/cite&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We’ve seen it with &lt;a href="http://jquerymobile.com/"&gt;jQuery Mobile&lt;/a&gt;, &lt;a href="https://cordova.apache.org/"&gt;Apache Cordova&lt;/a&gt; (&lt;a href="http://phonegap.com/"&gt;Phonegap&lt;/a&gt;), &lt;a href="http://ionicframework.com/"&gt;Ionic&lt;/a&gt;, &lt;a href="https://www.nativescript.org/"&gt;NativeScript&lt;/a&gt; and of course &lt;a href="http://www.reactnative.com/"&gt;React Native&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How do &lt;a href="https://developers.google.com/web/progressive-web-apps/"&gt;progressive web apps&lt;/a&gt; fit into the picture?&lt;/p&gt;

&lt;h2 id="what-is-it"&gt;What is it?&lt;/h2&gt;

&lt;p&gt;The fancy term was coined by Google where this technology originated. It sounds complex but progressive web apps are just web pages that use modern browser features – &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Manifest"&gt;Web App Manifest&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en/docs/Web/API/Service_Worker_API"&gt;Service Workers&lt;/a&gt; and HTTPS. They are usually complemented with &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Cache"&gt;Cache Storage&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en/docs/Web/API/IndexedDB_API"&gt;IndexedDB&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;They are linked with the browser and those technologies are currently experimental. At the moment you are mostly limited on Android platform as Safari doesn’t have a full support of service workers. You can check out the readiness of it at &lt;a href="https://jakearchibald.github.io/isserviceworkerready/"&gt;Is ServiceWorker Ready?&lt;/a&gt; site.&lt;/p&gt;

&lt;p&gt;The web manifest is a simple file with a few information about your app – icon, name, description and others. When you add the site to your phone home screen this is the file where the icon comes from.&lt;/p&gt;

&lt;p&gt;Service worker sits in between the browser and the server and is able to manage the network communication, work with the cache API and manage request when offline. It only works over HTTPS protocol.&lt;/p&gt;

&lt;p&gt;Progressive web applications can mimic the material design of Android applications, but in the end you are in charge of your HTML and CSS. For that you could use &lt;a href="https://www.polymer-project.org/1.0/docs/devguide/feature-overview"&gt;Polymer&lt;/a&gt; or one of the native look emulating javascript libraries like &lt;a href="http://ionicframework.com/"&gt;Ionic&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="why-would-you-want-to-use-it"&gt;Why would you want to use it?&lt;/h2&gt;

&lt;p&gt;Firstly, they won’t replace a mobile app. Or a website. They are a complement to them and are not faster to build or easier to maintain. They solve a different problem.&lt;/p&gt;

&lt;p&gt;They help to &lt;strong&gt;overcome the barrier to install and launch apps&lt;/strong&gt;. No need to go to the shop, search for the app, click install, agree on permissions and launch. You have them immediately in the browser and user can &lt;em&gt;bookmark&lt;/em&gt; them to the home screen.&lt;/p&gt;

&lt;p&gt;According to one of the Google presentations each step involved in installing an app has at least 20% drop out rate – meaning that with each click you lose 20% users. This is a huge! And for certain types of business like big e-shops, airlines or news it should be the reason to explore this area.&lt;/p&gt;

&lt;p&gt;When using your cellphone your connection will be flaky and slow even if you don’t live in &lt;a href="http://www.nzherald.co.nz/nz/news/article.cfm?c_id=1&amp;amp;objectid=10786737"&gt;New Zealand&lt;/a&gt;. Removing that from the equation leads to &lt;strong&gt;better conversions and more sales on flaky networks&lt;/strong&gt;. Users won’t leave your website because they’ve been staring at a blank screen for the past 3 minutes.&lt;/p&gt;

&lt;p&gt;Apart from improving the speed of the app you also have the ability to &lt;strong&gt;work purely offline&lt;/strong&gt;. That might be convenient for example for an airline app to show checked tickets.&lt;/p&gt;

&lt;p&gt;Prosper Otemuyiwa did a nice &lt;a href="https://auth0.com/blog/introduction-to-progressive-apps-part-one/"&gt;write up&lt;/a&gt; of existing use cases and how they improved the business.&lt;/p&gt;

&lt;h2 id="where-to-learn-more"&gt;Where to learn more?&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;a href="https://www.udacity.com/course/offline-web-applications--ud899"&gt;Offline Web Applications&lt;/a&gt; on Udacity by Google  – a short but useful introduction into Service Workers, Cache Storage and IndexedDB by Michael Wales and Jake Archibald.&lt;/li&gt;
  &lt;li&gt;Rossta’s &lt;a href="https://rossta.net/blog/make-your-rails-app-a-progressive-web-app.html"&gt;Progressive Web Application on Rails&lt;/a&gt; guide with a few additional resources to follow.&lt;/li&gt;
  &lt;li&gt;
&lt;a href="https://github.com/rossta/serviceworker-rails"&gt;serviceworker-rails&lt;/a&gt; is a gem from Rossta that provides the basic pieces for your offline app and integrates it into Rails asset pipeline.&lt;/li&gt;
  &lt;li&gt;
&lt;a href="https://jakearchibald.com/2014/offline-cookbook/"&gt;The Offline Cookbook&lt;/a&gt; by Jake Archibald shows different architecture patterns and approaches with their translation to Javascript.&lt;/li&gt;
  &lt;li&gt;Mozilla’s &lt;a href="https://serviceworke.rs/"&gt;Service Worker Cookbook&lt;/a&gt; is a great collection of patterns and examples on how to solve common problems like deferring POST requests while offline.&lt;/li&gt;
  &lt;li&gt;
&lt;a href="https://github.com/jakearchibald/idb"&gt;IndexedDB Promised&lt;/a&gt; wraps IndexedDB interface into a promise based architecture – nice to have if you want to interact with it. It keeps the interfaces same with the original so you can still use documentation for original &lt;a href="https://developer.mozilla.org/en/docs/Web/API/IndexedDB_API"&gt;IndexedDB API&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;
&lt;a href="https://youtu.be/qDJAz3IIq18"&gt;Instant-loading Offline-first&lt;/a&gt; from Progressive Web App Summit 2016 where Jake Archibald’s demonstrates the capabilities of Progressive Web Apps, converts an existing online application into a fully functional offline web app and compares the results.&lt;/li&gt;
  &lt;li&gt;
&lt;a href="https://youtu.be/U52dD0tegsA"&gt;Building Progressive Web Apps Today&lt;/a&gt; from Chrome Dev Summit 2016 where Thao Tran shares some success stories from customers like Flipkart and Alibaba and gives some guidance on how to make the money rain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Building a progressive web application requires the same amount of effort as creating a mobile app. You can’t easily convert your website into an app without major changes to the architecture and most of the early adopters solves it by creating and serving a mobile version different to the desktop one.&lt;/p&gt;

&lt;p&gt;You shouldn’t think of it as a replacement for a mobile app, but as a complement to your website that will help with customer engagement, conversions on flaky networks and remove the barrier to install on the phone.&lt;/p&gt;

&lt;p&gt;It is not a faster and cheaper way to develop mobile apps but it provides another way to support your business and generate more money.&lt;/p&gt;

&lt;h2 id="comments"&gt;Comments&lt;/h2&gt;

&lt;p&gt;Sintaxi: As one of the inventors of Cordova/PhoneGap allow me to set the record straight. We never intended for anyone to think PG was anything more than a temporary measure. Hence the mantra: “the ultimate purpose of PhoneGap is to cease to exist” –Brian LeRoux.&lt;/p&gt;

&lt;p&gt;If anything this article should be acknowledging the role Cordova/PG served as a conceptual if not functional proof of concept for progressive web apps.&lt;/p&gt;

&lt;p&gt;Jakub: You guys did a hell of a good job with Phonegap though. I think it is a wonderful piece of technology and it still has a great number of use cases.&lt;/p&gt;

&lt;p&gt;People like to think about these as silver bullets to mobile development which I dont think exists.&lt;/p&gt;
</content>
  </entry>
</feed>

