アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

CIで使っているMySQLのメモリ使用量を調整した話

最近CIが3回に1回くらいメモリ不足で落ちて辛かったのでメモリ使用量の見直しを行いました。

f:id:s4na:20210413180833p:plain

原因の特定

まずはエラーメッセージを確認します。

Codebuildのメッセージを見てみると、以下のように書かれていました。

# 〜中略〜

rails-test | /usr/local/bundle/gems/simplecov-0.21.0/lib/simplecov/source_file.rb:182:in `gets': Cannot allocate memory @ io_fillbuf - fd:9 /app/app/helpers/xxx_helper.rb (Errno::ENOMEM)

# 〜中略〜

testing_mysql2_1 exited with code 137

要約すると、MySQLが原因でメモリ不足が発生しているようです。

他のアプリがたくさんメモリを使っていることが原因の可能性もありますが、
とりあえずMySQLが落ちているみたいなので、まずはMySQLから見ていこうと思います。

見ていくポイントは以下の2点です。

  • CIで使っているインスタンスのメモリ容量がどれくらいあるか
  • そのうちどれくらい使っているのか

CIのメモリ容量を調べる

CIはCodebuildで行っています。
CodebuildのBuild detailsを確認したところ、15GBと書いてありました。

ということで今回は(MySQL以外も含めて)メモリ使用量を15G以下にしつつ、テストの実行時間が大幅に定価しなければ目標達成です。

MySQLのメモリ使用量を計算する

MySQLが使用するメモリ使用量は以下のサイトを参考に計算しました。

qiita.com

基本的な計算式としては、「グローバルバッファ + (スレッドバッファ x コネクション数)」です。

ただ、今回の場合Codebuildでは複数のMySQLが実行されています。
そのためDB台数をかける必要があります。

また、コネクション数ですが、Railsサーバーを複数個使っているので、それも考慮する必要がありそうです。

最終的に「グローバルバッファ x DB実行数 + (スレッドバッファ x コネクション数」という計算式で計算しました。

この計算式をもとに実際に現状の最大メモリ使用量を計算してみたところ

5G x 3 + 16M x 4000 = 79G

という数字が出てきました。

もちろんこれは全てのスレッドが最高負荷だったらという場合の話ですが、それにしても大きい数字ですね。
チューニングしがいがありそうです。😋

チューニングの方法

チューニングは以下の手順を回すことで行いました。
かなり慎重なやり方ですが、その分一つ一つの値がメモリ使用量に対してどう影響を与えるか、確認することができます。

  1. このパラメーターを変更したら、こういう結果が出るのではないか?という仮説を立てる
  2. 実際にパラメーターを変更
  3. CIを実行
  4. 結果をもとに、仮説の正しさを確認

チューニング内容

グローバルバッファ

今回の主な原因はグローバルバッファでした。

一度4000Mと誤って4000Gと入力して実行してしまった経験からすると、
グローバルバッファは設定してしまうと、使わない分もメモリを確保してしまうようです。

試しに少し減らしてみたところ、テストの実行時間に影響が出ず、メモリ使用量が減少しました。

スレッドバッファ

最終的に値を変更しませんでした。
試しに少し減らしてみたのですが、メモリ不足で失敗するテストが出てきたからです。

コネクション数

これがかなりの曲者でした。

まず、MySQL側のコネクション数を設定しても、それが最大値になりません。
というのも、Rails側でPool数を制限しているためです。
つまり、テスト全体のコネクション数は「RailsのPool数 x RailsのWorker数」で求められます。

ということでMySQL一つあたりのコネクション数を「RailsのPool数 x RailsのWorker数 / MySQLの台数」で求めたいのですが、その値を設定するとテストの実行時間が大幅に伸びてしまいました。

これは推測なのですが原因はテストの偏りだと思います。
RailsのWorkerが一つ一つのMySQLに対して均等にテストを実行するわけではないので、テストの実行数が元々実行していた数より減ってしまうようです。

ということで、「(RailsのPool数 x RailsのWorker数)〜(RailsのPool数 x RailsのWorker数 / MySQLの台数)」の値を設定しました。

最終テスト

仮説に仮説を重ねた状態ではあるものの、ある程度チューニングが終わりました。
最後にCIを5回ほど実行して以下の3点を確認しました。

  • メモリ不足で失敗しないこと
  • 実行時間が大幅に伸びないこと
  • メモリ使用量がギリギリにならないこと

チューニングする前の状態で3回に1回くらいはエラーで落ちてしまっていたので、これで落ちなければ落ちないのでは?と思います。

最終テスト成功

f:id:s4na:20210413180752p:plain

ということで無事CIが通るようになったので、チューニング終了です🎉🎉