<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hopsoft</title>
    <description>The latest articles on DEV Community by Hopsoft (@hopsoft).</description>
    <link>https://dev.to/hopsoft</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F95711%2Fdb98f7c5-5128-4836-99ae-6ffe6730af21.jpeg</url>
      <title>DEV Community: Hopsoft</title>
      <link>https://dev.to/hopsoft</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hopsoft"/>
    <language>en</language>
    <item>
      <title>Optimizing Rails connections</title>
      <dc:creator>Hopsoft</dc:creator>
      <pubDate>Fri, 18 Mar 2022 00:17:09 +0000</pubDate>
      <link>https://dev.to/hopsoft/optimizing-rails-connections-4gkd</link>
      <guid>https://dev.to/hopsoft/optimizing-rails-connections-4gkd</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the 3rd post in a series that &lt;a href="https://dev.to/hopsoft/distributed-systems-with-rails-36nm"&gt;starts here.&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Have you ever experienced a Rails app that suddenly and inexplicably delivers dismal response times or becomes completely unresponsive? It can be incredibly confusing, especially when nothing in the environment has changed recently. &lt;/p&gt;

&lt;p&gt;Let's look at one of the most common reasons this might happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Datastore Connections
&lt;/h2&gt;

&lt;p&gt;Correctly configuring Rails connections can be complicated. Here's a short video that explains how easy it is to get wrong. &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/XOfELjqm188"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Note that this problem isn't isolated to your primary database either. It's possible to misconfigure any datastore that has limited connections. Postgres, Redis, Memcached, etc...&lt;/p&gt;

&lt;p&gt;There are a few aspects of this problem to consider.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connection Limits
&lt;/h3&gt;

&lt;p&gt;Datastores have an upper limit to how many active connections they can reasonably support. System resources typically govern these limits. For example, &lt;a href="https://www.postgresql.org/docs/current/runtime-config-connection.html#RUNTIME-CONFIG-CONNECTION-SETTINGS" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; defaults to 100 connections while &lt;a href="https://redis.io/topics/clients#maximum-number-of-clients" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; defaults to 10,000. &lt;em&gt;It's possible to override default connection limits. Just be sure you know what you're doing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hosting providers impose further limits. Consider that Heroku's &lt;a href="https://elements.heroku.com/addons/heroku-postgresql#pricing" rel="noopener noreferrer"&gt;Standard 0&lt;/a&gt; Postgres plan caps at 120 connections and their &lt;a href="https://elements.heroku.com/addons/heroku-redis#pricing" rel="noopener noreferrer"&gt;Premium 0&lt;/a&gt; Redis plan caps at 40.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connection Pools
&lt;/h3&gt;

&lt;p&gt;Establishing a connection is an expensive and slow operation. &lt;a href="https://en.wikipedia.org/wiki/Connection_pool" rel="noopener noreferrer"&gt;Connection pooling&lt;/a&gt; reduces overall latency by limiting how frequently connections are set up by reusing pre-established connections.&lt;/p&gt;

&lt;p&gt;The improved efficiency gained from connection pooling enables applications to serve more concurrent requests than otherwise possible based on datastore connection limits alone. Pooling is a force multiplier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment Topology
&lt;/h3&gt;

&lt;p&gt;The deployment configuration plays a significant role. Consider a database with a limit of 40 connections. We'd be at capacity with 2 servers configured to use 20 connections each. &lt;/p&gt;

&lt;p&gt;This problem compounds as the deployment becomes more complex. And the challenge isn't limited to the number of servers. We also need to account for the number of processes running per server and the number of threads active in each process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Variables
&lt;/h2&gt;

&lt;p&gt;Typical Rails applications use the following environment variables.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WEB_CONCURRENCY&lt;/code&gt; - the number of processes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RAILS_MAX_THREADS&lt;/code&gt; - the number of threads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are descriptive names, but I think we can do better. Given that Rails applications do more than serve web requests, let's consider some more suitable names before looking at formulas.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SERVERS&lt;/code&gt; - the number of servers/dynos&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PROCESSES&lt;/code&gt; - the number of processes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;THREADS&lt;/code&gt; - the number of threads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also need to factor in external systems that have access to the database. This adds another variable to consider.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;EXTERNAL_CONNECTIONS&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's all the variables, now onto the formula.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Formula
&lt;/h2&gt;

&lt;p&gt;The formula is quite simple.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

(SERVERS * PROCESSES * THREADS) + EXTERNAL_CONNECTIONS


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's look at a standard Rails deployment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 web servers, 2 processes, 8 threads&lt;/li&gt;
&lt;li&gt;3 worker servers, 4 processes, 16 threads&lt;/li&gt;
&lt;li&gt;10 external connections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpopskiqfb44ge3ggayd3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpopskiqfb44ge3ggayd3.png" alt="Standard Rails Deployment"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Here's the equation for the number of connections.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

(2 * 2 * 8) + (3 * 4 * 16) + 10 = 234


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It's vital to ensure that you don't configure more connections than are available. &lt;em&gt;You may need to increase the provisioned hardware or reduce the number of connections your application uses.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;Here's an example of configuring Rails for the deployment described above.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# config/database.yml&lt;/span&gt;
&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql&lt;/span&gt;
  &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unicode&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV["THREADS"] %&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;# it's generally a good idea to configure timeouts&lt;/span&gt;
  &lt;span class="na"&gt;connect_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;statement_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
    &lt;span class="na"&gt;lock_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2s&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# config/initializers/sidekiq.rb&lt;/span&gt;
&lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"REDIS_QUEUE_URL"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; 
  &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"THREADS"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# pool size&lt;/span&gt;
  &lt;span class="c1"&gt;# it's generally a good idea to configure timeouts&lt;/span&gt;
  &lt;span class="ss"&gt;connect_timeout: &lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;read_timeout: &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;write_timeout: &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure_server&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure_client&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# config/environments/production.rb&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:redis_cache_store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"REDIS_CACHE_URL"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"THREADS"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# pool size&lt;/span&gt;
    &lt;span class="c1"&gt;# it's generally a good idea to configure timeouts&lt;/span&gt;
    &lt;span class="ss"&gt;connect_timeout: &lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;read_timeout: &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;write_timeout: &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# config/puma.rb&lt;/span&gt;
&lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"PROCESSES"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;max_threads_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"THREADS"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;min_threads_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"THREADS"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="n"&gt;min_threads_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_threads_count&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# Procfile&lt;/span&gt;
&lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PROCESSES=2 THREADS=8 bin/start-pgbouncer bundle exec puma -C config/puma.rb&lt;/span&gt;
&lt;span class="na"&gt;worker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PROCESSES=4 THREADS=16 bin/start-pgbouncer bundle exec sidekiq -C config/sidekiq.yml&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The formula and configuration may appear simple, but they are deceptively complex. The complexity becomes more apparent as the deployment &lt;a href="https://dev.to/hopsoft/distributed-systems-with-rails-36nm"&gt;grows more sophisticated&lt;/a&gt;. For example, consider a deployment with &lt;a href="https://dev.to/hopsoft/sophisticated-simple-and-affordable-background-workers-14l4"&gt;multiple worker pools&lt;/a&gt; of varying sizes backed by Redis instances with different connection limits. Can you think of other deployment topologies? How does the math work out?&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Options
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Stacked Pooling
&lt;/h3&gt;

&lt;p&gt;It's possible to stack connection pooling with tools like &lt;a href="http://www.pgbouncer.org/" rel="noopener noreferrer"&gt;pg_bouncer&lt;/a&gt;, which can help further ensure your Rails application doesn't exhaust database connections. Heroku supports this with a &lt;a href="https://devcenter.heroku.com/articles/on-dyno-postgres-connection-pooling" rel="noopener noreferrer"&gt;buildpack&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sidekiq Swarm
&lt;/h3&gt;

&lt;p&gt;The number of processes and threads can be hard to determine when using tools like &lt;a href="https://github.com/mperham/sidekiq/wiki/Ent-Multi-Process" rel="noopener noreferrer"&gt;Sidekiq swarm&lt;/a&gt;. Just pay attention and ensure that you know what the number is.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>database</category>
      <category>redis</category>
    </item>
    <item>
      <title>Sophisticated, Simple, and Affordable Background Workers</title>
      <dc:creator>Hopsoft</dc:creator>
      <pubDate>Fri, 11 Mar 2022 02:05:49 +0000</pubDate>
      <link>https://dev.to/hopsoft/sophisticated-simple-and-affordable-background-workers-14l4</link>
      <guid>https://dev.to/hopsoft/sophisticated-simple-and-affordable-background-workers-14l4</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the 2nd post in a series that &lt;a href="https://dev.to/hopsoft/distributed-systems-with-rails-36nm"&gt;starts here.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CodeFund
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://github.com/gitcoinco/code_fund_ads" rel="noopener noreferrer"&gt;CodeFund&lt;/a&gt;, we processed 5+ million background jobs per day at our peak. Some of those jobs performed computationally expensive operations. Others performed the heavy database work required to operate the business. We hosted on &lt;a href="https://heroku.com/" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt; with 3 &lt;a href="https://devcenter.heroku.com/articles/dyno-types" rel="noopener noreferrer"&gt;Standard 1x dynos&lt;/a&gt; responsible for background work. It cost $75/month and had plenty of capacity to scale without additional costs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9wini67yocgap32l9xy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9wini67yocgap32l9xy.png" alt="CodeFund"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How did we accomplish this?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first thing to consider is how best to separate the work. Think about the different types of jobs your application performs. Are there any jobs that should be isolated from the primary work queue? At CodeFund we pulled &lt;a href="https://github.com/gitcoinco/code_fund_ads/blob/master/app/jobs/create_debits_for_campaigns_job.rb" rel="noopener noreferrer"&gt;financial settlement&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Extract,_transform,_load" rel="noopener noreferrer"&gt;ETL processes&lt;/a&gt; out and set up a dedicated dyno to run these jobs on a schedule which allowed the primary worker dynos to focus on real-time operations, things like recording &lt;a href="https://github.com/gitcoinco/code_fund_ads/blob/master/app/jobs/create_impression_job.rb" rel="noopener noreferrer"&gt;ad impressions&lt;/a&gt; and &lt;a href="https://github.com/gitcoinco/code_fund_ads/blob/master/app/jobs/create_click_job.rb" rel="noopener noreferrer"&gt;clicks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's our Heroku worker dyno configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs3y6fyvhpy0kwe4fqtwn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs3y6fyvhpy0kwe4fqtwn.png" alt="CodeFund worker dynos"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;And here's how we configured Sidekiq queues.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/sidekiq.yml&lt;/span&gt;
&lt;span class="na"&gt;:concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="na"&gt;:queues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;critical&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mailers&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;schedule&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;impression&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;click&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;rollbar&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;action_mailbox_routing&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;active_storage_analysis&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;action_mailbox_incineration&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;low&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traffic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/sidekiq_data.yml&lt;/span&gt;
&lt;span class="na"&gt;:concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="na"&gt;:queues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;create_debits_for_campaign_and_date&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;create_debit_for_campaign_and_date&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;recalculate_organizataion_balances&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ensure_daily_summaries&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;daily_summaries&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;daily_summary&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ensure_scoped_daily_summaries&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note that we didn't use weights. Instead, we leveraged &lt;a href="https://github.com/mperham/sidekiq/wiki/Advanced-Options#queues" rel="noopener noreferrer"&gt;Sidekiq's default behavior&lt;/a&gt; of draining queues in linear order.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Considering the worker domain or category will help you organize queues into groups that can be configured and managed independently. For example, this relatively simple approach ensured that &lt;strong&gt;ETL processes&lt;/strong&gt; never blocked or slowed down &lt;strong&gt;real time&lt;/strong&gt; operations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Importing Third Party Data
&lt;/h2&gt;

&lt;p&gt;Most companies I've worked with need to import and standardize third-party data. How that data is obtained, how often it changes, and what's required to standardize can vary dramatically. &lt;/p&gt;

&lt;p&gt;Third party data providers often impose restrictions like rate-limiting to prevent problems within their own systems. They typically require a means of authentication to enforce such controls. Sometimes upstream data is volatile and requires continuous importing.&lt;/p&gt;

&lt;p&gt;I've worked on applications where several of our customers shared credentials for accessing third party data with other customers. Sharing credentials can prove challenging, given data access limits, and is further compounded if customers have different requirements for data freshness and accuracy.&lt;/p&gt;

&lt;p&gt;Standardizing and augmenting this data can be expensive. It can also trigger large quantities of adjacent work. I've seen small upstream changes that cascaded into millions of jobs for a single customer, which flooded the queue and blocked other critical work.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can we solve these problems?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I tend to use similar strategies whenever I encounter requirements like this. I lean on Sidekiq &lt;a href="https://sidekiq.org/products/pro.html" rel="noopener noreferrer"&gt;Pro&lt;/a&gt; and &lt;a href="https://sidekiq.org/products/enterprise.html" rel="noopener noreferrer"&gt;Enterprise&lt;/a&gt; features, use great libraries like &lt;a href="https://github.com/fractaledmind/acidic_job" rel="noopener noreferrer"&gt;AcidicJob&lt;/a&gt;, and apply my own Rails-fu when needed.&lt;/p&gt;

&lt;p&gt;Let's tackle each problem in turn.&lt;/p&gt;

&lt;h3&gt;
  
  
  Honor Third Party Constraints
&lt;/h3&gt;

&lt;p&gt;Begin with the expectation that some customers will share credentials for accessing third-party data and that rate limits exist for accessing that data. We'll need to throttle requests for all customers sharing the same third party credentials. &lt;/p&gt;

&lt;p&gt;Let's create an ActiveRecord model to manage credentials and include the ability to throttle activity. We'll use Sidekiq Enterprise's &lt;a href="https://github.com/mperham/sidekiq/wiki/Ent-Rate-Limiting#bucket" rel="noopener noreferrer"&gt;bucket-based rate limiting&lt;/a&gt; to accomplish this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/credential.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Credential&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:customers&lt;/span&gt;

  &lt;span class="c1"&gt;# Throttles the passed block for this credential&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;wait_timeout: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="ss"&gt;lock_timeout: &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Limiter&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_gid_param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;within_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/jobs/fetch_data_job.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FetchDataJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveJob&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throttle&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="c1"&gt;# 1. fetch third-party data&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="c1"&gt;# 2. queue adjacent work&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;FetchDataJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that we don't DOS third parties, even when our customers share credentials and request data at the same time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevent Customers from Consuming all Resources
&lt;/h3&gt;

&lt;p&gt;Remember that importing data triggers a cascade of adjacent work. We need to ensure that a single customer doesn't flood the queue(s), consume all system resources, and block other important work.&lt;/p&gt;

&lt;p&gt;We can manage this with clever use of Sidekiq &lt;a href="https://github.com/mperham/sidekiq/wiki/Advanced-Options#queues" rel="noopener noreferrer"&gt;queue weights&lt;/a&gt;. First, let's update our Sidekiq configuration and set up partitions for the &lt;code&gt;default&lt;/code&gt; and &lt;code&gt;low&lt;/code&gt; queues.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/sidekiq.yml&lt;/span&gt;
&lt;span class="na"&gt;:concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;
&lt;span class="na"&gt;:queues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;critical&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;6&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;high&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;default_0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;default_1&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;default_2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;default_3&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;low&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;low_0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;low_1&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;low_2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;low_3&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the partitions for &lt;code&gt;default&lt;/code&gt; and &lt;code&gt;low&lt;/code&gt; queues share the same weights. This means that jobs in these queues will have an equal chance of being dequeued &lt;em&gt;when multiple customers are queueing millions of jobs in parallel&lt;/em&gt;. A single customer won't block others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So how do we get jobs into a partitioned queue?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's create a helper method to obtain a random partitioned queue name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/sidekiq.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;partitioned_queue_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;rand&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's look at how to best use these partitioned queues. We'll revisit our &lt;code&gt;FetchDataJob&lt;/code&gt; and create a few others to handle the adjacent work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/jobs/fetch_data_job.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FetchDataJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;source_documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throttle&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="c1"&gt;# fetch third-party data and assign source_documents&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;source_documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="no"&gt;StandardizeDocumentJob&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="ss"&gt;queue: &lt;/span&gt;&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;partitioned_queue_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="ss"&gt;wait: &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we're queueing up adjacent work into &lt;code&gt;StandardizeDocumentJob&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/jobs/standardize_document_job.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StandardizeDocumentJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="c1"&gt;# standardize the source document's data&lt;/span&gt;
    &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;

    &lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="c1"&gt;# scan document for linked images&lt;/span&gt;

    &lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="no"&gt;ImportImageJob&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="ss"&gt;queue: &lt;/span&gt;&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;partitioned_queue_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="ss"&gt;wait: &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The work then cascades and fans out to &lt;code&gt;ImportImageJob&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/jobs/import_image_job.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImportImageJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# save url to cloud provider&lt;/span&gt;
    &lt;span class="c1"&gt;# update document&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We only throttle the request to fetch third party data&lt;/li&gt;
&lt;li&gt;After the initial import, the work cascades exponentially&lt;/li&gt;
&lt;li&gt;Partitioned jobs have equal priority if enqueued at around the same time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's a lot to digest, but the primary takeaway is to &lt;strong&gt;be thoughtful when architecting background workers&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Your solution can be as simple or as elaborate as needed. And we've barely scratched the surface of what's possible here. What if we configured our worker infrastructure to run the &lt;code&gt;low&lt;/code&gt; queue on a large cluster of servers. &lt;em&gt;How might that affect throughput?&lt;/em&gt; I've partitioned worker pools similar to PostgreSQL's Hash based &lt;a href="https://www.postgresql.org/docs/current/ddl-partitioning.html" rel="noopener noreferrer"&gt;table partitioning&lt;/a&gt; to help prevent individual customers or processes from blocking others.&lt;br&gt;
 &lt;em&gt;What about other infrastructure changes?&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Next up, we'll learn about &lt;a href="https://dev.to/hopsoft/optimizing-rails-connections-4gkd"&gt;configuring Rails connections&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>sidekiq</category>
    </item>
    <item>
      <title>Distributed Systems with Rails</title>
      <dc:creator>Hopsoft</dc:creator>
      <pubDate>Thu, 10 Mar 2022 20:57:15 +0000</pubDate>
      <link>https://dev.to/hopsoft/distributed-systems-with-rails-36nm</link>
      <guid>https://dev.to/hopsoft/distributed-systems-with-rails-36nm</guid>
      <description>&lt;p&gt;When most people think of &lt;strong&gt;distributed systems&lt;/strong&gt;, domains like &lt;a href="https://en.wikipedia.org/wiki/Microservices" rel="noopener noreferrer"&gt;microservices&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Blockchain" rel="noopener noreferrer"&gt;blockchains&lt;/a&gt; or some variant of orchestrated complexity come to mind. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxordwelxtqsit65folk5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxordwelxtqsit65folk5.png" alt="Distributed system complexity"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;But let's take a moment to consider &lt;a href="https://en.wikipedia.org/wiki/Distributed_computing" rel="noopener noreferrer"&gt;Wikipedia's definition&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A distributed system is a system whose components are located on different networked computers, which communicate and coordinate their actions by passing messages to one another from any system. The components interact with one another in order to achieve a common goal.&lt;/p&gt;

&lt;p&gt;Three significant characteristics of distributed systems are: &lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  1. concurrency of components
  2. lack of a global clock
  3. independent failure of components
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;If we stretch this definition a bit, we'll discover that Ruby on Rails supports creating distributed systems. In fact, most full-stack web frameworks do. &lt;/p&gt;

&lt;p&gt;Consider that you're already running multiple services, often on separate hardware, things like databases and caches. Now examine purpose-driven deployments. A server dedicated to standard web requests and another for API requests. Or think of the third-party services that web applications rely on for things like logging, email, search, analytics, image processing, etc... Such services are endless. Just look at what's offered in &lt;a href="https://elements.heroku.com/addons" rel="noopener noreferrer"&gt;Heroku's marketplace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcxm0tyd3296fqak5uh7z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcxm0tyd3296fqak5uh7z.png" alt="Typical full stack deployment"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;While it may not be self-evident, thinking of Rails as a distributed system platform will help frame the mental model for the ensuing discussion. &lt;/p&gt;




&lt;p&gt;I want to focus on a particular feature in Rails that helps accomplish some distributed system characteristics, &lt;a href="https://edgeguides.rubyonrails.org/active_job_basics.html" rel="noopener noreferrer"&gt;ActiveJob&lt;/a&gt;. ActiveJob provides background job processing for Rails. &lt;/p&gt;

&lt;p&gt;The principal component in the Rails toolbelt for managing background jobs is the backplane or &lt;a href="https://en.wikipedia.org/wiki/Software_bus" rel="noopener noreferrer"&gt;data-bus&lt;/a&gt; which allows us to store metadata about expensive operations that should move off the &lt;a href="https://seebrock3r.medium.com/a-hot-path-is-a-code-path-that-is-perf-critical-either-because-its-something-that-is-really-85ee62fe68f6" rel="noopener noreferrer"&gt;critical path&lt;/a&gt; and be processed in the background. These days, &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; is generally used as this backplane for serious Rails applications. It's a multipurpose in-memory data store that serves several functions: a cache, message broker, and database. &lt;/p&gt;

&lt;p&gt;ActiveJob uses Redis as a &lt;a href="https://en.wikipedia.org/wiki/Message_broker" rel="noopener noreferrer"&gt;message broker&lt;/a&gt; by saving or queueing metadata about operations to run in the background. It also dequeues that metadata and runs those operations. &lt;a href="https://sidekiq.org/" rel="noopener noreferrer"&gt;Sidekiq&lt;/a&gt; is another popular library that serves the same purpose. Sidekiq implements the ActiveJob interface and includes several advanced features not available in ActiveJob.&lt;/p&gt;

&lt;p&gt;It's common for Rails applications to run ActiveJob background workers on separate hardware, but sophisticated deployments might even run isolated worker pools on different machines for different types of background work. &lt;em&gt;It's also viable to run multiple instances of Redis to further separate different types of background work.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm1tjmcuzwxn6185cou4d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm1tjmcuzwxn6185cou4d.png" alt="Rails based distributed system deployment"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The possibilities are endless, and Rails supports all of them. I hope it's becoming clear that purpose-driven deployments can comprise a distributed system even when all the code is contained inside a monolith. For our purposes, it might help to think of distributed systems in terms of &lt;strong&gt;units of deployment&lt;/strong&gt; instead of isolated &lt;strong&gt;units of execution&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;Now, let's consider some scenarios where segregating different types of background work makes sense.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limiting resource contention on a centralized database&lt;/li&gt;
&lt;li&gt;Constraining requests to third party API endpoints&lt;/li&gt;
&lt;li&gt;Partitioning customers to ensure a single user can't exhaust system resources and cause problems for everyone&lt;/li&gt;
&lt;li&gt;Isolating ETL processes to prevent disrupting production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these scenarios can be managed by a single monolithic Rails codebase. &lt;strong&gt;Why might this be a good idea?&lt;/strong&gt; Team productivity is exceptionally high in a well-designed Rails monolith. Even if the code is more tightly coupled, it's a tradeoff worth considering as you'll get several benefits of &lt;br&gt;
&lt;strong&gt;"distributed systems"&lt;/strong&gt; without all the downsides.&lt;/p&gt;




&lt;p&gt;Stay tuned for the &lt;a href="https://dev.to/hopsoft/sophisticated-simple-and-affordable-background-workers-14l4"&gt;next post&lt;/a&gt; covering how to structure a queueing system to manage all that background work.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Rails, Hotwire, CableReady, and StimulusReflex are BFFs</title>
      <dc:creator>Hopsoft</dc:creator>
      <pubDate>Mon, 08 Nov 2021 16:27:46 +0000</pubDate>
      <link>https://dev.to/hopsoft/rails-hotwire-cableready-and-stimulusreflex-are-bffs-4a89</link>
      <guid>https://dev.to/hopsoft/rails-hotwire-cableready-and-stimulusreflex-are-bffs-4a89</guid>
      <description>&lt;p&gt;&lt;strong&gt;Enforcing strict RESTful routes and controllers is perhaps the most impactful technique that influenced my usage of &lt;a href="https://rubyonrails.org/"&gt;Ruby on Rails&lt;/a&gt; for the better.&lt;/strong&gt; I cannot overstate how much I love traditional &lt;a href="https://en.wikipedia.org/wiki/Representational_state_transfer"&gt;REST&lt;/a&gt; semantics and encourage their usage on every team that I have influence over. &lt;/p&gt;

&lt;p&gt;Having said that, I also think rigidly applying this pattern to smaller and smaller use cases has diminishing returns. One example of a smaller use case is &lt;a href="https://turbo.hotwired.dev/reference/streams"&gt;TurboFrames&lt;/a&gt;. TurboFrames are great and I use them along with their attendant REST semantics, but I try to be very thoughtful about how far I take this approach.&lt;/p&gt;

&lt;p&gt;For example, libs like &lt;a href="https://cableready.stimulusreflex.com/"&gt;CableReady&lt;/a&gt; and &lt;a href="https://github.com/stimulusreflex/futurism"&gt;Futurism&lt;/a&gt; can lazy load partials so unobtrusively that the notion of adhering to the formality of REST, &lt;em&gt;with the required new routes, controllers, etc...&lt;/em&gt;, would be far too much ceremony for such small use cases.&lt;/p&gt;

&lt;p&gt;One of the original goals of CableReady and &lt;a href="https://docs.stimulusreflex.com/"&gt;StimulusReflex&lt;/a&gt; was to work seamlessly with traditional HTTP server rendered Rails apps &lt;em&gt;(pre Hotwire)&lt;/em&gt; without requiring significant architectural changes or forcing a proliferation of new routes, controllers, or views/partials etc... We basically wanted a way to gradually introduce robust real-time and reactive behavior into traditional Rails apps with as little friction as possible. The idea being to allow people to leverage the work that had already been done rather than forcing a rethinking of the app. I view CableReady/StimulusReflex as: &lt;strong&gt;REST + &lt;a href="https://en.wikipedia.org/wiki/Remote_procedure_call"&gt;RPC&lt;/a&gt; sprinkles + async server triggered DOM behavior&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hotwired.dev/"&gt;Hotwire&lt;/a&gt;, while very cool, introduces new concepts that impose a higher cognitive cost and forces you to rethink how to best structure a Rails app. I view Hotwire as: &lt;strong&gt;REST semantics for everything + async server triggered CRUD updates&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are pros and cons to each approach. Hotwire has more obvious and strict conventions, while CableReady and StimulusReflex adhere more to Ruby's philosophy of flexibility and expressiveness.&lt;/p&gt;

&lt;p&gt;For me, using both Hotwire and CableReady + StimulusReflex techniques together is like &lt;em&gt;&lt;a href="https://en.wikipedia.org/wiki/You_can't_have_your_cake_and_eat_it"&gt;"having my cake and eating it too."&lt;/a&gt;&lt;/em&gt; Admitedly, this is a power move and requires some experience to know when to apply each approach.&lt;/p&gt;

&lt;p&gt;FYI - There are some great conversations on the &lt;a href="https://discord.gg/stimulus-reflex"&gt;StimulusReflex Discord&lt;/a&gt; server about this stuff. We'd love it if you joined us.&lt;/p&gt;




&lt;p&gt;Also, I should note how much I dislike the umbrella marketing term &lt;strong&gt;"Hotwire"&lt;/strong&gt; as it forces a false dichotomy in this conversation. Both CableReady and StimulusReflex are designed to work well with Hotwire libs and even have hard dependencies on some of them.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>hotwire</category>
      <category>stimulusreflex</category>
      <category>cableready</category>
    </item>
    <item>
      <title>Build a real-time Twitter clone in 10 mins with Rails and StimulusReflex</title>
      <dc:creator>Hopsoft</dc:creator>
      <pubDate>Wed, 29 Apr 2020 14:07:20 +0000</pubDate>
      <link>https://dev.to/codefund/build-a-real-time-twitter-clone-10-mins-with-rails-and-stimulusreflex-5h5c</link>
      <guid>https://dev.to/codefund/build-a-real-time-twitter-clone-10-mins-with-rails-and-stimulusreflex-5h5c</guid>
      <description>&lt;h1&gt;
  
  
  Demo
&lt;/h1&gt;

&lt;p&gt;Learn how to build interactive real-time applications with &lt;a href="https://rubyonrails.org"&gt;Ruby on Rails&lt;/a&gt;, &lt;a href="https://github.com/hopsoft/cable_ready"&gt;CableReady&lt;/a&gt;, and &lt;a href="https://github.com/hopsoft/stimulus_reflex"&gt;StimulusReflex&lt;/a&gt;. We'll create a Twitter style timeline that broadcasts updates to all users in real-time ...and we'll build the entire application in less than 10 minutes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hopsoft/stimulus_reflex"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oaKW4AYR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bgvlrgmp8lj7ucyigxap.png" alt="StimulusReflex"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/F5hA79vKE_E"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  Conceptual Compression
&lt;/h1&gt;

&lt;p&gt;These tools and techniques take &lt;a href="https://m.signalvnoise.com/conceptual-compression-means-beginners-dont-need-to-know-sql-hallelujah/"&gt;conceptual compression&lt;/a&gt; to a new level by helping developers of all experience levels build real-time reactive applications faster than ever. We've been able to accomplish this with a sustained focus on &lt;strong&gt;developer happiness&lt;/strong&gt; and &lt;strong&gt;productivity&lt;/strong&gt; that has reduced much of the complexity associated with "modern" web development.&lt;/p&gt;

&lt;p&gt;We use these tools at &lt;a href="https://codefund.io"&gt;CodeFund&lt;/a&gt; to build some of the most sophisticated parts of our application. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7XW9Rnch--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lo5gwvdh1x3fbhio5fyk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7XW9Rnch--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lo5gwvdh1x3fbhio5fyk.png" alt="CodeFund Form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The best part is that we're open source, so you can see exactly how we do it. For example, here's the backend reflex code used to build the form shown in the video. &lt;a href="https://github.com/gitcoinco/code_fund_ads/blob/master/app/reflexes/campaign_bundles_reflex.rb"&gt;https://github.com/gitcoinco/code_fund_ads/blob/master/app/reflexes/campaign_bundles_reflex.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep an eye out for an in depth write up on the &lt;a href="https://codefund.io/blog"&gt;CodeFund blog&lt;/a&gt; where we'll dig into more detail. Coming soon...&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>rails</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The Ideal Bootstrapped Business </title>
      <dc:creator>Hopsoft</dc:creator>
      <pubDate>Mon, 03 Feb 2020 14:33:11 +0000</pubDate>
      <link>https://dev.to/hopsoft/the-ideal-bootstrapped-business-592n</link>
      <guid>https://dev.to/hopsoft/the-ideal-bootstrapped-business-592n</guid>
      <description>&lt;p&gt;I recently watched Jason Cohen's excellent presentation from &lt;a href="https://microconf.com"&gt;MicroConf&lt;/a&gt; in &lt;a href="https://microconf.com/videos-2013"&gt;2013&lt;/a&gt;. Jason is a successful entrepreneur and has founded several companies including &lt;a href="https://wpengine.com"&gt;WP Engine&lt;/a&gt; and &lt;a href="https://smartbear.com"&gt;Smart Bear&lt;/a&gt;. His presentation was chock full of great information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vimeo.com/74338272"&gt;https://vimeo.com/74338272&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are my notes. Hopefully you'll find them useful.&lt;/p&gt;




&lt;p&gt;Most companies don't work. They either fail to build a product that people want, or they build something that doesn't embrace the constraints of a small company.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Self-Funded Startup
&lt;/h1&gt;

&lt;p&gt;A self-funded startup is a &lt;strong&gt;cash machine&lt;/strong&gt; with &lt;strong&gt;predictable&lt;/strong&gt; acquisition of &lt;strong&gt;recurring&lt;/strong&gt; revenue that offers &lt;strong&gt;annual&lt;/strong&gt; prepay in a &lt;strong&gt;good market&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Revenue Models
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🚫 &lt;strong&gt;One Offs&lt;/strong&gt; - Every month starts at zero and requires new sales efforts... the opposite of a cash machine.&lt;/li&gt;
&lt;li&gt;🚫 &lt;strong&gt;Picking Up Pennies&lt;/strong&gt; - Skimming a percentage from other financial activity i.e. donations, transactions, etc... an inefficient cash machine.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Recurring Revenue&lt;/strong&gt; - The only way to build a cash machine.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Getting 1,000 customers at any price point is really hard. &lt;strong&gt;150 customers&lt;/strong&gt; is more realistic to start.&lt;/p&gt;

&lt;h3&gt;
  
  
  150 Customers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;50 from scratching and clawing&lt;/li&gt;
&lt;li&gt;25 from social media marketing&lt;/li&gt;
&lt;li&gt;75 from standard marketing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Average Revenue per User (ARPU)&lt;/strong&gt; is the most important metric that you can track while you're small. Set a goal to hit $10,000 in monthly recurring revenue with 150 customers. That means your pricing should average $66/mo per customer. &lt;/p&gt;

&lt;p&gt;Founders are hesitant to charge enough because they know their product is shitty in the beginning. Find ways to justify a higher price. Also, embrace the fact that you're running a &lt;strong&gt;boutique&lt;/strong&gt; business. People are willing to pay more to help small companies they believe in.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pricing Tactics
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tiers&lt;/strong&gt; - monthly/quarterly/yearly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specials&lt;/strong&gt; - higher monthly price with various coupons and discounts&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Annual Prepay&lt;/strong&gt; - will solve cash flow problems&lt;/p&gt;

&lt;p&gt;If just 25% of your customers elect annual prepay, it will yield over &lt;strong&gt;3x the cash flow&lt;/strong&gt; that monthly plans provide. Here are some tactics to convince more customers to opt for annual prepay.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Annual Only Coupons&lt;/strong&gt; - 3 months off&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High Monthly + 1/2 off Annual&lt;/strong&gt; - raise monthly prices &amp;amp; offer steep annual discounts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business Tier&lt;/strong&gt; - name the most expensive plan "Business"... some will opt for this simply because they want to spend the most&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Revenue Tactics
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Money Back Guarantee&lt;/strong&gt; - will produce more revenue than free trials.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Premium Support&lt;/strong&gt; - paid offer to move a customer's support tickets to a priority position in the queue. Doesn't require additional labor but is very much a value add for the customer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Market Models
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🚫 &lt;strong&gt;Consumer (B2C)&lt;/strong&gt; - Extremely low price and very high maintenance.&lt;/li&gt;
&lt;li&gt;🚫 &lt;strong&gt;Point-in-Time&lt;/strong&gt; - Pain points are temporary and not recurring or sustainable. &lt;em&gt;Weddings, events, etc...&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;🚫 &lt;strong&gt;Viral&lt;/strong&gt; - Very low probability of actual virality or revenue.&lt;/li&gt;
&lt;li&gt;🚫 &lt;strong&gt;Marketplace&lt;/strong&gt; - Difficult to build. Requires creating two different businesses and balancing their growth.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Naturally Recurring&lt;/strong&gt; - Anything tied to regular cycles or underlying volatility. &lt;em&gt;Hosting costs, marketing tools, invoicing, reporting, SEO, HR/compliance, etc...&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Naturally Latent&lt;/strong&gt; Anything without real-time requirements. &lt;em&gt;Decision support (analytics &amp;amp; reports), content, project management, etc...&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Completable&lt;/strong&gt; - Smaller product scope mitigates feature creep and allow you to focus on customers without racing to add every competitor's features.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;After Market&lt;/strong&gt; - Add ons to an existing product or service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose a &lt;strong&gt;big market&lt;/strong&gt;. Product validation already exists and customers are easier to find. There's plenty of money and room for competition. Servicing a niche in a big market provides the flexibility to pivot or expand as needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acquisition Models
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🚫 &lt;strong&gt;Social Media&lt;/strong&gt; - Very expensive (time and money) and doesn't produce predictable recurring revenue.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Paid Advertising&lt;/strong&gt; - Very predictable and repeatable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rules of thumb for acquisition costs. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low bar is a $300 spend for $50/mo in revenue.&lt;/li&gt;
&lt;li&gt;Basic CPC (Cost per Click) Formula: &lt;code&gt;CPC = Monthly ARPU / 25&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Your formulas will become more sophisticated as you experiment and gather data.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  End Game
&lt;/h2&gt;

&lt;p&gt;Successful companies continue to grow. They don't stop. You need a plan.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sell and take your exit&lt;/li&gt;
&lt;li&gt;Deliberately limit growth (raise prices, waiting list, etc... will change your clientele)&lt;/li&gt;
&lt;li&gt;Raise money to fuel accelerated growth&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>startup</category>
      <category>business</category>
      <category>entrepreneurship</category>
    </item>
    <item>
      <title>Ruby is designed for humans</title>
      <dc:creator>Hopsoft</dc:creator>
      <pubDate>Sun, 19 Jan 2020 15:53:56 +0000</pubDate>
      <link>https://dev.to/hopsoft/ruby-is-designed-for-humans-2o4</link>
      <guid>https://dev.to/hopsoft/ruby-is-designed-for-humans-2o4</guid>
      <description>&lt;p&gt;I just finished reading this &lt;a href="https://evrone.com/yukihiro-matsumoto-interview"&gt;great interview with Matz&lt;/a&gt;.  It helps build an understanding of his thought process behind the design of the Ruby programming language. Here's a short excerpt.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;People don’t understand the nature of designing a language. Language is kind of a scaffolding of the mind, a way of structuring your thinking. It’s same to human languages, like Russian, Japanese and English.&lt;/p&gt;

&lt;p&gt;Programming languages such as Ruby, Python, JavaScript, and so on, help to develop the mind, to allow you to turn ideas into something tangible and useful. That’s the primary purpose of a programming language.&lt;/p&gt;

&lt;p&gt;I think a programming language should have a philosophy of helping our thinking, and so Ruby’s focus is on productivity and the joy of programming. Other programming languages, for example, focus instead on simplicity, performance, or something like that. Each programming language has a different philosophy and design. If you feel comfortable with Ruby’s philosophy, that means Ruby is your language.&lt;/p&gt;

&lt;p&gt;-Matz&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the philosophical foundation that I attempted to communicate in my post, &lt;a href="https://dev.to/hopsoft/why-i-still-love-ruby-2in8"&gt;Why I still love Ruby&lt;/a&gt;. Matz simplifies the message even further. ❤️&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why I still love Ruby</title>
      <dc:creator>Hopsoft</dc:creator>
      <pubDate>Mon, 13 Jan 2020 14:58:08 +0000</pubDate>
      <link>https://dev.to/hopsoft/why-i-still-love-ruby-2in8</link>
      <guid>https://dev.to/hopsoft/why-i-still-love-ruby-2in8</guid>
      <description>&lt;h2&gt;
  
  
  Language and Cognition
&lt;/h2&gt;

&lt;p&gt;Philosophers and linguists have long asked the question, &lt;strong&gt;Does the language we speak shape the way we think?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We now have academic research to help answer this fascinating question. The data seems to indicate that language does indeed affect the way we think. It can even impact our ability to perform.&lt;/p&gt;

&lt;p&gt;Lera Boroditsky, Ph.D has a wonderful TED talk that introduces linguistic and cognitive studies to the masses. &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/RKK7wGAYP6k"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;She demonstrates that language influences a tremendous amount of the way we perceive and interact with the world. Everything from our sense of direction to our understanding of time and distance. It touches our ability to count or quantify, our awareness of colors, our perception of intent, credit and blame, even our concepts of gender.&lt;/p&gt;

&lt;p&gt;Our language changes what we pay attention to and notice. What we place emphasis on. What our biases are. It impacts our capacity and speed at which we can accomplish various tasks. It can even change the physical characteristics of our brain.&lt;/p&gt;

&lt;p&gt;Language acts as a stepping stone into entirely new cognitive realms and can lead to big differences in creative and intellectual ability. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Ruby Language
&lt;/h2&gt;

&lt;p&gt;Yukihiro Matsumoto (Matz) has indicated that Ruby was inspired and influenced by several other programming languages. Lisp, Smalltalk, Perl, Eiffel, Ada, Basic, and others. More recently, Rust, Go, and Elixir. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I knew many languages before I created Ruby, but I was never fully satisfied with them. They were uglier, tougher, more complex, or more simple than I expected.&lt;/p&gt;

&lt;p&gt;Throughout the development of the Ruby language, I’ve focused my energies on making programming faster and easier.&lt;/p&gt;

&lt;p&gt;Ruby is designed to make programmers happy.&lt;/p&gt;

&lt;p&gt;-Matz&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Why I still love Ruby
&lt;/h3&gt;

&lt;p&gt;Ruby has a long history of assimilating the best ideas, patterns, and practices from other programming languages. In many ways, it is the convergence of the most exceptional features from other languages. &lt;/p&gt;

&lt;p&gt;Admittedly this can feel messy or chaotic at times, but it delivers the widest range of options to the programmer. Ruby's culture of embracing and including the best from other languages dramatically increases the solution space and directly impacts our capacity to think and perform as developers.&lt;/p&gt;

&lt;p&gt;Does this mean we should ignore other languages and trust the MRI team will always get it right? Of course not. Learning another language will improve your ability to work with Ruby... or any language for that matter.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To have a second language is to have a second soul. &lt;/p&gt;

&lt;p&gt;-Charlemagne&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Having said that, Ruby is the most malleable and flexible language I've ever worked with. It frees my mind to explore solutions that I may not have considered or even been aware of in another language.&lt;/p&gt;

&lt;h4&gt;
  
  
  A short note on Rails
&lt;/h4&gt;

&lt;p&gt;I should point out that Rails wouldn't exist without Ruby. DHH appropriated Ruby's culture of inclusivity and created a framework that is a delightful melting pot of tools, patterns, and techniques. &lt;strong&gt;All made possible by Ruby.&lt;/strong&gt; &lt;em&gt;Language choice may also help explain why no other web framework has materialized that shares the same eminence and stamina.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is why I still love Ruby, even after 10 years of working with it.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Build a server updated async progress bar with Rails in 5 steps</title>
      <dc:creator>Hopsoft</dc:creator>
      <pubDate>Tue, 07 Jan 2020 15:18:03 +0000</pubDate>
      <link>https://dev.to/codefund/build-an-async-progress-bar-with-rails-in-5-steps-2bgp</link>
      <guid>https://dev.to/codefund/build-an-async-progress-bar-with-rails-in-5-steps-2bgp</guid>
      <description>&lt;p&gt;This tutorial demonstrates how simple it is to perform DOM updates from Rails background jobs with &lt;a href="https://github.com/hopsoft/cable_ready" rel="noopener noreferrer"&gt;CableReady&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F7yczivl0n4hks0zn0smj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F7yczivl0n4hks0zn0smj.gif" alt="Progress Bar Demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Intro
&lt;/h1&gt;

&lt;p&gt;Ruby on Rails supports websockets out of the box via a built in library known as &lt;a href="https://guides.rubyonrails.org/action_cable_overview.html" rel="noopener noreferrer"&gt;ActionCable&lt;/a&gt;. I created a library named &lt;a href="https://github.com/hopsoft/cable_ready" rel="noopener noreferrer"&gt;CableReady&lt;/a&gt; that works with ActionCable to perform common DOM operations from background jobs without requiring you to write any custom JavaScript. And, it's very performant.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create the Rails project
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails new progress_bar_demo
&lt;span class="nb"&gt;cd &lt;/span&gt;progress_bar_demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Create the &lt;a href="https://en.wikipedia.org/wiki/Representational_state_transfer" rel="noopener noreferrer"&gt;restful&lt;/a&gt; resource
&lt;/h2&gt;

&lt;p&gt;First create the controller and HTML page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails generate controller progress_bars
&lt;span class="nb"&gt;touch &lt;/span&gt;app/views/progress_bars/show.html.erb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- app/views/progress_bars/show.html.erb --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Progress Bar Demo&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"progress-bar"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update the routes file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="ss"&gt;:progress_bar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:show&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="s2"&gt;"progress_bars#show"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Setup the styling
&lt;/h2&gt;

&lt;p&gt;First create the stylesheet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;app/javascript/stylesheets
&lt;span class="nb"&gt;touch &lt;/span&gt;app/javascript/stylesheets/application.scss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/stylesheets/application.scss&lt;/span&gt;
&lt;span class="nn"&gt;#progress-bar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#ccc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;13px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nn"&gt;#progress-bar&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;green&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update the JavaScript pack to include the stylesheet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/packs/application.js&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@rails/ujs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;turbolinks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@rails/activestorage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;channels&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../stylesheets/application.scss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- add this line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, update the application layout to use the stylesheet pack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- app/views/layouts/application.html.erb --&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;ProgressBarDemo&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;csrf_meta_tags&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;csp_meta_tag&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- line below was updated to use stylesheet_pack_tag --&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;stylesheet_pack_tag&lt;/span&gt; &lt;span class="s1"&gt;'application'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;media: &lt;/span&gt;&lt;span class="s1"&gt;'all'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'data-turbolinks-track'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'reload'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_pack_tag&lt;/span&gt; &lt;span class="s1"&gt;'application'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'data-turbolinks-track'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'reload'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Setup the ActionCable channel
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add cable_ready
bundle exec rails generate channel progress_bar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/channels/progress_bar_channel.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;consumer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./consumer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CableReady&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cable_ready&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ProgressBarChannel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cableReady&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;CableReady&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;operations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/channels/progress_bar_channel.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProgressBarChannel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationCable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Channel&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribed&lt;/span&gt;
    &lt;span class="n"&gt;stream_from&lt;/span&gt; &lt;span class="s2"&gt;"ProgressBarChannel"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Setup the backend
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle add cable_ready
bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails generate job progress_bar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this job fires, it runs a loop that fills in the progress bar a little bit on each iteration. This is possible because CableReady allows us to send commands to the browser that update the DOM without the need to write custom Javascript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/jobs/progress_bar_job.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProgressBarJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;CableReady&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Broadcaster&lt;/span&gt;
  &lt;span class="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:default&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
      &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
      &lt;span class="n"&gt;cable_ready&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ProgressBarChannel"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;selector: &lt;/span&gt;&lt;span class="s2"&gt;"#progress-bar&amp;gt;div"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s2"&gt;"width:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;cable_ready&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast&lt;/span&gt;
      &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# fake some latency&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/progress_bars_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProgressBarsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="no"&gt;ProgressBarJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;wait: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;second&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Run and watch the magic
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then visit &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; in a browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;p&gt;⚠️ This demo is tailored for the development environment. In a production setup you'd need to configure both ActionCable and ActiveJob to use Redis. You'd also want to secure the ActionCable channel.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
