<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://illuminatedcomputing.com/">
  <id>https://illuminatedcomputing.com/</id>
  <title>Illuminated Computing</title>
  <updated>2017-04-12T00:00:00Z</updated>
  <link rel="alternate" href="https://illuminatedcomputing.com/" type="text/html"/>
  <link rel="self" href="https://illuminatedcomputing.com/tags/sidekiq/atom.xml" type="application/atom+xml"/>
  <author>
    <name>Paul A. Jungwirth</name>
    <uri>https://illuminatedcommputing.com/</uri>
  </author>
  <entry>
    <id>tag:illuminatedcomputing.com,2017-04-12:/posts/2017/04/scaling-sidekiq/</id>
    <title type="html">Scaling Sidekiq</title>
    <published>2017-04-12T00:00:00Z</published>
    <updated>2017-04-12T00:00:00Z</updated>
    <link rel="alternate" href="https://illuminatedcomputing.com/posts/2017/04/scaling-sidekiq/" type="text/html"/>
    <content type="html">
&lt;p&gt;&lt;a href="https://github.com/mperham/sidekiq"&gt;Sidekiq&lt;/a&gt; is a great option for handling background jobs in Ruby projects. Here I’ll show you how to get the best utilization out of a box dedicated to running Sidekiq jobs. Whether you have one machine or ten, the goal is to work off as many jobs as possible from each machine.&lt;/p&gt;

&lt;h2 id="cpu_utilization"&gt;CPU Utilization&lt;/h2&gt;

&lt;p&gt;To do that, we want to keep every core busy. You can monitor your CPU activity with a tool like &lt;a href="https://linux.die.net/man/8/vmstat"&gt;vmstat(8)&lt;/a&gt;. If you say &lt;code&gt;vmstat 10&lt;/code&gt;, you’ll get a new row every ten seconds, like so:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 9  0  92056 553620 174128 6268208    0    0     1    25 1323 1443 23 77  0  0  0
 8  0  92056 552628 174128 6268492    0    0    14    38 1307 1447 22 78  0  0  0
 8  0  92056 551760 174132 6268672    0    0     3    49 1317 1461 23 77  0  0  0
 8  0  92056 550544 174140 6268832    0    0     1    28 1353 1490 23 77  0  0  0
 8  0  92056 550296 174148 6268940    0    0     1    35 1240 1360 22 78  0  0  0&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The last few columns show percentage of CPU time doing user work (&lt;code&gt;us&lt;/code&gt;), system work (&lt;code&gt;sy&lt;/code&gt;)—which more or less means executing kernel system calls—, idle (&lt;code&gt;id&lt;/code&gt;), and blocked on I/O (&lt;code&gt;wa&lt;/code&gt;, “waiting”). Add the first two together to get how much your CPU is working.&lt;/p&gt;

&lt;p&gt;There is also &lt;code&gt;st&lt;/code&gt;, which means “stolen time”! If you are running a VM, this means the time your hypervisor gives to someone else. You will probably see all zeros in this column, so I’ll ignore it going forward. We want to get our idle and wait time as close to zero as possible, without drowning the machine in too much work. Above we have a machine that is doing a good job keeping busy. You can see that its user and system time are at 100, and the other columns are zero.&lt;/p&gt;

&lt;p&gt;So how do we do this? Usually it means running lots of jobs at the same time, not one-after-another. Even a one-core machine can juggle many jobs. That’s because jobs typically have to block on I/O, for example when they talk to the network or save things to disk. While that job is waiting for an answer, your CPU can work on another one.&lt;/p&gt;

&lt;p&gt;(This is not really relevant here, but &lt;a href="http://www.linux-mag.com/id/308/"&gt;disk I/O is a little different than other I/O&lt;/a&gt;. &lt;a href="https://www.remlab.net/op/nonblock.shtml"&gt;Technically, reading/writing with a regular file can’t “block” but only “sleep”&lt;/a&gt;. That &lt;a href="http://neugierig.org/software/blog/2011/12/nonblocking-disk-io.html"&gt;completely messes up non-blocking I/O for regular files&lt;/a&gt;, and &lt;a href="http://lse.sourceforge.net/io/aio.html"&gt;even the newer &lt;code&gt;aio&lt;/code&gt; functions have many limitations&lt;/a&gt; and in fact are &lt;a href="http://man7.org/linux/man-pages/man7/aio.7.html"&gt;implemented by threads in userspace&lt;/a&gt;—but fortunately we are not talking about single-threaded non-blocking I/O; we are talking about multiple processes/threads. Whether you call it blocking or sleeping, the CPU will still schedule different work if something is stuck on a regular file &lt;code&gt;read&lt;/code&gt;. And if this paragraph doesn’t make sense, feel free to dismiss it as a pedantic footnote. :-)&lt;/p&gt;

&lt;h2 id="threads"&gt;Threads&lt;/h2&gt;

&lt;p&gt;Sidekiq is great here because it supports multi-threading. Multiple threads let you do concurrent work &lt;em&gt;in one Ruby process&lt;/em&gt; (at least as long as you are not &lt;a href="http://www.csinaction.com/2014/10/10/multithreading-in-the-mri-ruby-interpreter/"&gt;still stuck on Ruby 1.8&lt;/a&gt;). Without threads you’d need a separate process for each concurrent job, and that can use up memory quickly, especially with something like Rails. It is always sad to have more CPU available that you can’t use because you’re out of RAM.&lt;/p&gt;

&lt;p&gt;In practice, threads mostly help if you are using a concurrent Ruby implementation like JRuby. MRI Ruby has a Global Interpreter Lock (GIL), which prevents two threads from executing at once. Still, even in MRI you will still see some benefit, because when one thread blocks on I/O, MRI can make progress on another. So despite the GIL, MRI can still make sure at least &lt;em&gt;some&lt;/em&gt; thread is running.&lt;/p&gt;

&lt;p&gt;With Sidekiq, you can say how many threads to run with the &lt;code&gt;concurrency&lt;/code&gt; setting. Normally you’d set this in your &lt;code&gt;sidekiq.yml&lt;/code&gt; file. Note that each thread needs its own database connection! That means if your &lt;code&gt;concurrency&lt;/code&gt; is 10, then in your &lt;code&gt;database.yml&lt;/code&gt; you must have a &lt;code&gt;pool&lt;/code&gt; of 10 also (or more). Otherwise the threads will halt each other waiting to check out a database connection, and what is the point of that? They might even get timeout errors.&lt;/p&gt;

&lt;h2 id="processes"&gt;Processes&lt;/h2&gt;

&lt;p&gt;But wait, there’s more! We can push the &lt;code&gt;concurrency&lt;/code&gt; up and up, and still see something like this from vmstat:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0  92048 585840 174148 6269120    0    0     0    16  956  990  7 18 75  0  0
 1  0  92048 585212 174160 6269256    0    0     1    23 1012 1037  8 17 75  0  0
 1  0  92048 580456 174164 6269312    0    0     1    33  896  884  8 17 75  0  0&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Why is the idle CPU time stuck at 75?&lt;/p&gt;

&lt;p&gt;It turns out this is a four-core machine, and even with multiple threads, a single MRI process can only use one core, because of the GIL. That’s not what we want at all!&lt;/p&gt;

&lt;p&gt;So the answer is to run more processes—at least one per core. You can see how many cores you’ve got by saying &lt;code&gt;cat /proc/cpuinfo&lt;/code&gt;. If you are using something like &lt;a href="http://godrb.com/"&gt;&lt;code&gt;god&lt;/code&gt;&lt;/a&gt;, it is easy to put several processes into a group and control them together, like so:&lt;/p&gt;

&lt;div class="CodeRay"&gt;&lt;div class="code"&gt;&lt;pre&gt;&lt;code class="language-ruby"&gt;app_root = &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;/var/www/myapp/current&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class="integer"&gt;4&lt;/span&gt;.times &lt;span class="keyword"&gt;do&lt;/span&gt; |i|
  &lt;span class="constant"&gt;God&lt;/span&gt;.watch &lt;span class="keyword"&gt;do&lt;/span&gt; |w|
    w.name     = &lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;myapp-sidekiq-&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;#{&lt;/span&gt;i&lt;span class="inline-delimiter"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;
    w.group    = &lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;myapp-sidekiq&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;
    w.log      = &lt;span class="constant"&gt;File&lt;/span&gt;.join(app_root, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;log&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;#{&lt;/span&gt;w.name&lt;span class="inline-delimiter"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="content"&gt;.log&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;)
    w.pid_file = &lt;span class="constant"&gt;File&lt;/span&gt;.join(app_root, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;current&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;tmp&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;pids&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;#{&lt;/span&gt;w.name&lt;span class="inline-delimiter"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="content"&gt;.pid&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;)
    w.start    = &lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;lt;&amp;lt;-EOS&lt;/span&gt;&lt;/span&gt;.gsub(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="char"&gt;\n&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt; &lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;)&lt;span class="string"&gt;&lt;span class="content"&gt;
      cd '&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;#{&lt;/span&gt;app_root&lt;span class="inline-delimiter"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="content"&gt;' &amp;amp;&amp;amp;
        bundle exec ./bin/sidekiq --environment production
                                  --pidfile '&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;#{&lt;/span&gt;w.pid_file&lt;span class="inline-delimiter"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="content"&gt;'
                                  --logfile '&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;#{&lt;/span&gt;w.log&lt;span class="inline-delimiter"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="content"&gt;'
                                  --daemon&lt;/span&gt;&lt;span class="delimiter"&gt;
    EOS&lt;/span&gt;&lt;/span&gt;
    &lt;span class="comment"&gt;# ...&lt;/span&gt;
  &lt;span class="keyword"&gt;end&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you aren’t using god and don’t know how to do this with your own process manager, then I think &lt;a href="https://github.com/mperham/sidekiq/wiki/Ent-Multi-Process"&gt;Sidekiq Enterprise has a similar feature called Swarms&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="scheduling_cores"&gt;Scheduling cores&lt;/h2&gt;

&lt;p&gt;That gets us almost there, but there is still a problem. You might &lt;em&gt;still&lt;/em&gt; see idle time stuck somewhere, like (on a 4-core system) 25. The problem is that while the kernel does its best to use your whole CPU, it can still wind up putting two Sidekiq processes on the same core, and since they are such long-lived processes, they are stuck that way, competing for their shared core while another one sits idle. The kernel doesn’t know ahead of time that they are going to run for days and keep so busy.&lt;/p&gt;

&lt;p&gt;Fortunately we can still force each process onto its own core. For that we use the &lt;a href="https://linux.die.net/man/1/taskset"&gt;taskset(1)&lt;/a&gt; command. When you say &lt;code&gt;taskset -c 2 date&lt;/code&gt;, you are telling Linux to run &lt;code&gt;date&lt;/code&gt; on core 2. (Core numbers start from zero, as you can see in &lt;code&gt;/proc/cpuinfo&lt;/code&gt;.) So our god config would become:&lt;/p&gt;

&lt;div class="CodeRay"&gt;&lt;div class="code"&gt;&lt;pre&gt;&lt;code class="language-ruby"&gt;w.start    = &lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;lt;&amp;lt;-EOS&lt;/span&gt;&lt;/span&gt;.gsub(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="char"&gt;\n&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt; &lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;)&lt;span class="string"&gt;&lt;span class="content"&gt;
  cd '&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;#{&lt;/span&gt;app_root&lt;span class="inline-delimiter"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="content"&gt;' &amp;amp;&amp;amp;
    bundle exec taskset -c &lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;#{&lt;/span&gt;i&lt;span class="inline-delimiter"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="content"&gt; ./bin/sidekiq --environment production
                                              --pidfile '&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;#{&lt;/span&gt;w.pid_file&lt;span class="inline-delimiter"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="content"&gt;'
                                              --logfile '&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;#{&lt;/span&gt;w.log&lt;span class="inline-delimiter"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="content"&gt;'
                                              --daemon&lt;/span&gt;&lt;span class="delimiter"&gt;
EOS&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After that, we’ll have one process on each core. At this point, you should start experimenting with your &lt;code&gt;concurrency&lt;/code&gt; setting, to make each core fully utilized. The right setting there will depend on how much blocking a job does, but I have seen useful numbers up to 20. Just try some things out, and watch &lt;code&gt;vmstat&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note that you &lt;em&gt;don’t&lt;/em&gt; have to increase the connection pool size in &lt;code&gt;database.yml&lt;/code&gt; as you add cores (just concurrency). That’s because each process is a separate thing, each with its own pool. But you &lt;em&gt;do&lt;/em&gt; have to increase the max connections on your database server. For instance with Postgres you’d want to set &lt;code&gt;max_connections&lt;/code&gt; in &lt;code&gt;postgresql.conf&lt;/code&gt;. Here you need to allow enough connections for &lt;code&gt;concurrency&lt;/code&gt; times cores (times servers), plus some more for your actual Rails app serving web requests, plus some more for anything else you’ve got going on. That can be a lot of connections! Don’t be surprised if improving your job throughput exposes a bottleneck elsewhere in your system.&lt;/p&gt;

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

&lt;p&gt;Tuning Sidekiq can be complicated, because of the double layers of threads plus processes. You need a confident understanding of how Ruby and your operating system handle concurrency, and it helps to use tools like &lt;code&gt;vmstat&lt;/code&gt; to measure what’s going on and verify your understanding. If you have a box dedicated to just Sidekiq jobs, my recommendation is to run one process per core, using &lt;code&gt;taskset&lt;/code&gt; to keep them separate, and then tune &lt;code&gt;concurrency&lt;/code&gt; from there. Hopefully this will help with your own projects!&lt;/p&gt;
</content>
  </entry>
</feed>

