PumaやSidekiqの設定で必要となる前提知識を絵で理解する

2022/11/27に公開

1. はじめに

本記事は、これからPumaやSidekiqの設定を考えていく必要があり、「以下の記事に有用そうな何かが書かれている気がするがどういうことだろう」と頭を悩ませている方に向けて、元ネタの内容を私なりに噛み砕いて整理した記事になります。正確性より概念的理解を重視してまとめており、大枠を理解した上で元記事を読んでいただくと、理解が捗るかと思います。

2. 「Rubyのスケール時にGVLの特性を効果的に活用する(翻訳)」を理解する

2-1. Rubyのコードを実行する仕組み

Rubyのコードはインタプリタによって解釈され、Ruby VMへの指示に変換されます。そして、Ruby VMがその指示をCPUへの指示に変換することで、Rubyのプログラムは実行されています。

2-2. パラレリズムとコンカレンシー

パラレリズムは複数の人間が同時に仕事をする状態を指し、コンカレンシーは一人の人間が複数の仕事をする状態を指します。

複数の人間が同時に仕事をするパラレリズムだと、時間当たりに処理される仕事は多くなりますが、コンカレンシーの場合だと時間当たりに処理される仕事量は変わりません。

2-3. アムダールの法則

並列に処理できる部分の割合が多いと、並列化する旨味は大きいよねと言う法則です。

絵を描くと、なぜそうなるのか、どういう数式になるのかが簡単に理解でき、並列化可能部分の割合をP、並列数をnとすると、性能向上率SS = \frac{1}{1 - P + \frac{P}{n}}になります。

これを横軸:並列数、縦軸:性能向上率にしてプロットすると、以下のようなグラフになるわけです。(Wikipediaより)

2-4. GVLとは(Ruby 3.0以降はthread_schedに改名)

Ruby VMはスレッドセーフではないので、Ruby VMが同時に処理できるのは1スレッドだけです。ですので、1つのスレッドを処理している間は、ロックをかけて他のスレッドを実行しないようにしています(= GVL / thread_sched)。つまり、複数スレッドを立てても、パラレルに実行されている訳ではなく、コンカレントに実行されている訳です。

ですが、Ruby VMが複数スレッドをパラレルに処理できる時間もあり、それは処理していたスレッドでIO関連のシステムコールを行ってGVLが解放されたときです。データベースやネットワーク呼び出しなどの待ち時間中はコンピュータが仕事をしていないので、この時間中もロックをかけておくのは勿体ないということで、この待ち時間はロックを解放して他のスレッドを処理させる仕様になっています。

と言うことは、IO待ち時間が並列化可能部分であり、このIO待ち時間が占める割合が高いほど、並列化による旨味が大きいことになります。

2-5. バックグラウンドジョブとWebサーバをマルチスレッドにする理由

ここまでくると、この節の結論は簡単で、Webアプリケーションではリクエストの処理中にデータベースなどのIO待ち時間が発生しているだろうから、マルチスレッドにしておくと並列化の恩恵が受けられるよねと言うことになります。

そして、大体の場合、バックグラウンドジョブ(Sidekiqなど)はWebサーバよりもIO待ち時間が長い処理(CSVインポートなど)を受け持つ場合が多いので、きっと、Webサーバのスレッド数よりバックグラウンドジョブのスレッド数を多くした方が良いよねということになります。

3. 「Rails: Puma/Unicorn/Passengerの効率を最大化する設定(翻訳)」を理解する

3-1. Pumaの子プロセス数

クラスターモードを利用して、1つのアプリケーションサーバにつきプロセスを3つ以上にすると、パフォーマンスが上がります。

と言うのも、ロードバランサーはサーバの死活は見ているものの、サーバ上のpumaプロセスがbusyかどうかまでは見ていないので、サーバ上に1つのpumaプロセスしか存在しない場合、リクエストがbusyなpumaプロセスに割り当てられてしまう可能性があるからです。一方で、サーバ上に複数のpumaプロセスを起動させておけば、それら全てのプロセスがbusyでない限りは、リクエストがbusyなプロセスに割り当てられることはなくなります。

では、プロセスの数をどこまで増やせば良いかというと、それはサーバのCPUとメモリリソースによって制限されます。

3-2. Pumaのスレッド数

Pumaをマルチスレッドにした方が良い理由とスレッド数は処理時間に対するIO待ち時間の割合によって決めた方が良い理由は2-5節で説明した通りです。では、具体的に何スレッドにすると良いかと言うと、プロセスあたりのスレッド数を5に設定してあとは忘れるで良いそうです。と言うのも、大体のWebアプリでは総時間の10~25%をIO待ちが占めるため、アムダールの法則的に6スレッド以上に増やすメリットはさほどないからです。
例えば、並列可能部分の割合P = 0.25の場合、並列化度n = 5にすると性能向上率はS = 1.25となり、n = 50にすると性能向上率S = 1.32になります。

3-3. プリロード

マルチワーカーモードの場合はメモリを節約するためにも、preload_app!を設定した方が良い(アプリが1つ以上のワーカーを使用する場合、デフォルトでオンになっている)です。と言うのも、pumaのマスタープロセスでアプリケーションを全て読み込んでおけば、親・子プロセスのメモリが変更されるまではメモリが共有される(copy-on-write)からです。

4. まとめ

本記事では、備忘録も兼ねて、PumaやSidekiqの設定チューニングする際に必要となる理解を絵を描きながらまとめてみました。この記事で図に描いたことを想像しながら元記事を読むと、す〜っと理解できるかもしれません。元記事を読んだものの理解するのが難しく感じた方にとって、お役に立てれば幸いです。

Discussion