ボクココ

少数サービス開発運用に関するテックブログ

0->1 サービス開発者の慣れないインデックス貼り

ども、@kimihom です。

f:id:cevid_cpp:20220124160525j:plain

本記事は、普段アプリケーションエンジニアとして 0->1 でのサービス開発をやってきて、クエリ処理が重くなってどうしたものかという状況へ達した際に考えたいことをまとめてみる。

インフラエンジニアか自分でやるか

まず、サービスを0から開始して、DB が重たく感じるくらいには たくさんのユーザーが使われる Web, スマホ アプリとなっておめでとう!ここまで来られただけで、本当に素晴らしい 0->1 エンジニアだと断言できよう。

さて、DB のクエリ処理が重たいと感じるくらい使われるようになったら、そこで 0->1 エンジニアの役割は終わりとなるのか。

大抵の 0->1 エンジニアの場合、答えは Yes となる。なぜならこうした運用作業は、未経験の領域で、それより新しいものをどんどんと開発していくのが好きなエンジニアだからである。 「次開発するサービスのフレームワークは、最近流行ってる〇〇を使おう!」 「それでブログ書いたらまたバズるぜ、ウヒヒ」 そんなことを企む素晴らしい意欲がある。

さて、そんな 0->1 エンジニア を繰り返すと、あることに気づいてくる。 「このサービスを最初に作ったけど、それが金額としてなぜ戻ってこないのか」 「もっと評価されるべきじゃないの」 と。この点が 0->1 エンジニア の勘違いしやすいポイントとなっていることは間違いない。 "サービス開発をしてそれなりにユーザー来て、今後成長していく見込みもある" という状態だけでは、残念ながらまだお金を払っていただけるたくさんのユーザーを得られていない。

だからこそ、サービスが中規模になってきてもしっかりと運用ができるようになるエンジニアとなることが必要だ。つまり、DB が重くなってきたら、"自分" がその DB を最適化させていく必要がある。

インデックスをしっかりと理解しよう

「インデックス?それってテーブル作ったらアクセスが速くするものでしょ」Rails アプリを作っていると、そのくらいの認識でいるかと思われる。かつての私もそうだった。

正直なところ、データ数が10万程度だったら、それでなんとかなる。10万もデータがあるサービスを 0->1 エンジニアが運営しているという環境がどのくらいあるのか不明だが、私の体験として引き続き記していく。

なんかこのクエリが重たくてタイムアウトしてるなという事象が、どんどんと発生してくる。そしたらようやくインデックスについて真面目に知らないといけないなと危機感を覚えるようになる。 「今まで Active Record に頼ってきて、急に データベース のインフラを知らなければならないなんて・・」 と絶望する。

さぁ、インデックスをしっかりと理解しよう!今まで重かった SQL が、ものすごく軽くなった時の喜びが、そこにはある。

重たいクエリの検出とその対応

簡単に手順をまとめておこう。まずは重たいクエリをより正確に把握する必要がある。

私は Rails メインでやっているので、Rails 中心でシンプルなモニタリングサービスである AppSignal を利用している。

www.appsignal.com

AppSignal でなくても、大抵のモニタリングサービスと連携させると、それだけで重たくて修正した方がいいSQLリストを発見できる。

さて、ここでまず何をするのかというと、その重たいクエリが現状どういう状況なのかを確認する必要がある。PostgreSQLのケースで説明していくと、以下。

EXPLAIN ANALYZE SELECT * FROM tables WHERE hoge = 1 AND fuga = 2 LIMIT 10; 

EXPLAIN ANALYZE で、具体的にどんなインデックスがそのクエリで使われたのかを分析することが可能となる。大抵の場合、何もインデックスが効いていないシーケンシャルスキャンとなっているはず。

SELECT, WHERE
・Seq Scan … 全行スキャンしている(インデックスを使用していない)
・Index Scan  … インデックスを使用している
・Bitmap Scan  … (例えば複数の)インデックスを効率的に使用してスキャンしている
・Index Only Scan … テーブルのアクセスを省略して検索。非常に高速

ではインデックスを貼ろうとなり、インデックスを作ることになるが、ここで複合インデックスについては必ず理解することをお勧めする。Heroku ドキュメント にて

複数列インデックスが明確に意味を持つ状況があります。列 (a, b)​ のインデックスは、WHERE a = x AND b = y​ を含むクエリ、または WHERE a = x​ のみを使用するクエリで使用できますが、WHERE b = y​ を使用するクエリでは使用されません。そのため、これがアプリケーションのクエリパターンと一致する場合は、複数列インデックスのアプローチを検討する価値があります。この場合、a​ のみにインデックスを作成すると冗長になることにも注意してください。

こういう決まりがあるので、しっかりと理解して最適なインデックスを最小限作れるように努力しなければならない。1つインデックスを作っただけだけど、そのインデックスで効果の出るクエリが3つになったということが起こる。 また、既存のインデックスを適用させるために、ORDER の順番を意図的に変えたり WHERE であえて限定させるクエリに変える。それだけで速くなったりもする。

さて、決断ができたら インデックスを貼ろう。

class AddIndexToTables < ActiveRecord::Migration
  def change
    add_index :tables, [:hoge, :fuga]
  end
end

$ rails db:migrate

この程度であれば、きっと 0->1 エンジニアでも、まずは基本のインデックス対応ができるはずだ。

終わりに

今回は 0->1 エンジニアが DB 対応をするときにまず知っておきたいことを記した。

改めて、0->1 エンジニアがその時点までこれただけで、ものすごいこと!

本記事は、その先へ進むためのヒントになれば、幸いだ。

さぁ、これからの時代は 0->1 エンジニアではなく、0->100 エンジニア だ!!