こちらは DRECOM Advent Calendar 2024 12/19の記事です
ドリコム20新卒サーバーエンジニアのやっちこと岡村です。
業務でUnityを触ってたりするのでサーバーとクライアントの境界が溶けたエンジニアをしています。



突然ですが皆さんはDBの負荷削減を行っていますか?

負荷削減自体はリファクタの延長にあるので、どうしても後手になりがちでビジネス的にもあまり優先事項にならないことが多く放置されがちです。(よほどUXが悪いとかあると優先度は上がったりしますが)

本記事では負荷削減のメリットとこれまでに筆者が負荷削減をしてくる中で出会ってきたありがちな実装方法(Ruby on Railsでの)を紹介していきたいと思います。

※今回の負荷削減は一般的なクライアント(フロント)からアクセスする(オートスケールする)APIサーバーとDBの構成を想定しています。
※本記事ではMySQLの設定に関する具体的な内容については言及しません。


負荷削減とはなにか


今回話す負荷削減はアプリケーションのロジックを改善することで、以下を実現することを指すこととします。

  • DBのCPU使用率やI/Oなどの各種メトリクスの改善

上記を実現できることで

  • DBのスペックを下げることの検討
  • 通信回数の削減によるAPIサーバーのオートスケールの台数削減
  • 急なユーザー数の増加時の安定稼働
  • UX改善

などのビジネス的なメリットを提示することが可能となります。
特に昨今の円安によりAWSなどの費用も数年前と比べて高騰しているため費用削減はかなり重要だと考えられます。

例えばAWSのRDSのインスタンスタイプをdb.r7g.12xlargeからdb.r7g.8xlargeに落とすことができれば、7.885USD/h から5.324USD/hの削減となり1USD=150円とすると約28万円/月の削減になり、年間で340万円ほどのコスト削減になります。(RDSには他の料金もあるのであくまでも概算ですが)
レプリケーションをしていたらその倍の効果となるので只事ではありません。

※ Aurora MySQL互換エディションのオンデマンドインスタンスをap-northeast-1で使用する想定 https://aws.amazon.com/jp/rds/aurora/pricing/?pg=pr&loc=1

負荷の原因になりがちな実装パターン


ここからは実際に筆者が出会ってきた負荷の原因になるパターンを紹介していきたいと思います。

その一 N+1(とそれに近いもの)


has_manyなどのリレーションをもつレコードを複数件SELECTしてRuby側でループを回す際に毎回子レコードをSELECTしてしまうことでSELECTが肥大化する実装です。
レコード数が増えるたびにSQLを叩く回数が増大するので運用する中で徐々にボトルネックになっていきます。

load前のassociationの呼び出しを検知するbullet gemを用いることで検知可能ですが、なんらかの理由でリレーションを設定していない場合などは地味に発見が難しいです。
特に何らかのクエリを叩く共通処理用の処理が深いところで呼ばれているとうっかりループ中に呼び出してしまうことはかなり多いです。

その二 1件ずつのinsert/update


複数件のレコードをbulk insert/updateをせずにループしてinsert/updateする実装。
bulk insert/updateのデメリットとしては実行時にバリデーションがスキップされたりbulk insertでの新しいカラムのIDが取得できないなどがあります。(MySQL)

その三 不適切なインデックス


適切なカーディナリティのカラムにインデックスが設定されておらずSELECTが遅くなるインデックスの不足状態。
また不要なインデックスによるinsert/updateのパフォーマンスの悪化状態。
運用してデータが増えてから顕著に発生してしまうのでslow queryのログなどで見つけたいですね。

その四 更新対象にすべきではないレコードに対するupdate


bulk updateなどの際に、更新していないレコードもbulk updateの対象にしてしまい、余計なupdated_atカラムのみの更新クエリを叩く実装。
地味に全体のパフォーマンスに影響してしまうことがあります。

その五 クライアント(フロント)からの全件取得


クライアントから、何かしらのアクションで都度全件取得するサーバーAPIを叩いている実装。
更新差分の取得できないかを検討することが多い印象です。
負荷削減のためならサーバーエンジニアでもクライアントの実装をしましょう。

これだけではないのですが、API実装時のサーバーエンジニアの認識とクライアント実装時の認識の齟齬もあるあるなので気をつけたいですね。


おわりに


今回は負荷削減のメリットと実際に見てきたものを簡単に紹介しました。
正直そこまで目新しいものはなく、あるある系のものばかりでしたが、プロジェクトの規模が大きくなると見落としてしまうことがあるので気を付けて実装していきたいですね。
本記事を通して負荷削減にこれまで向き合って来なかった人が少しでも増えてくれたら嬉しいです。

ドリコムでは一緒に働くメンバーを募集しています!募集一覧はコチラを御覧ください!