パフォーマンスチューニング: 手を動かす前に考える

この記事は Akerun Advent Calendar 2021 - Qiita の16日目の記事です。

こんにちは。@ps-tsh です。API Server などバックエンドシステムの開発を担当しています。

フォトシンスも創業から8年目に突入し、サービスを安定稼働させるためにパフォーマンスチューニングを行う機会も増えてきました。開発当時はしっかりテストしてリリースしたプロダクトであっても、運用が長期化すると「ファーストビューは速いが、ページングするとだんだん遅くなっていく」「1分で終了していたバッチ処理に15分かかるようになってきた」といった課題が発生します。特に、ビジネス面がうまくいくほどユーザ数や履歴データの蓄積ペースも上がるので、システムにかかる負荷は急激に増大します。

そんなわけで今回は、パフォーマンスチューニングの話、とくに「コードを直す前」の話をしようと思います。

取り組む前に

パフォーマンスチューニングに取り組んだエンジニアに話を聞いてみると、よく「N+1が発生していたので解消しました」「インデックスを貼って検索を速くしました」といった回答が返ってきます。決して間違いではないのですが、ときどき「ちょっと手段の実践に傾倒しているな」と思うことがあります。手段の実践が先行すると、原因がわからない問題を調べるときにも「N+1がないか確認します」「インデックスが貼られているか確認します」のように、手段を軸とした調査になってしまい、当てが外れた時に打ち手がなくなってしまいます。つまり、手持ちの道具が限られていることで、対応できる課題の幅も狭まってしまうという罠に陥るのです。パフォーマンス問題を解決する方法は、DBクエリのチューニングに限られません。

チューニングで行き詰まったら、まずは一歩引いて問題を俯瞰してみましょう。いちど席を立って深呼吸し、コーヒーを淹れて飲んだりするのも良いかもしれません。

性能目標を確認する

チューニングに取り組むにあたり、最初にやることは「性能目標を確認する」です。これ、意外とみんなやっていないんですよね。ひとくちに「遅い」「重い」というのは簡単ですが、よく話を聞いてみたら「処理データ件数を考えると妥当な水準」「決して早くはないが、特段の実害もない」というケースも結構あります。あるいは、もともと性能目標が定められていないケースもあると思います。そんな場合でも「どれくらいになればひとまずOKといえるのか」を明確にしましょう。現在地と目的地があって初めて、取れる手段を適切に選べるのです。なお、性能目標はレスポンスタイムやスループットだけではありません。たとえば「表面上は性能に問題がなくても、裏ではCPU利用率が100%に張り付いてしまうことがあり、常時50%以下に押さえたい」といった状況の解消が目標になることもあります。

最初に目標を確認するのには、もう一つ理由があります。「チューニングは楽しい」のです。具体的に困っている人がいる状況で、解決すれば感謝される。技術的スキルを発揮する場としては申し分ありませんし、エンジニアとして最善の解決策を追求したいというマインドも理解できます。しかし、チューニング作業には時間的な制約を伴うトラブルシュートという側面もあります。

常時稼働のSaaSにおいては1週間かけて100%の解決策を提供するより、まず2時間で70%を救えるアプローチがあるならそちらを選ぶ状況もあります(この場合、最終的には暫定対応と恒久対応の二段構えで100%にもっていきます)。チューニングの実施においては技術的な正解ばかりを優先するのではなく、現実的な解決策としての有効性が求められます。「いま最優先で求められているのは何か」の視点をもち、本来の目標にフォーカスしましょう。

測定する

次にやることは「問題の事象を測定する、再現性を確認する」です。実際に起こっていない問題を解決することはできないからです。測定の過程では実測値と一緒に、問題となっている事象の発生条件も確認します。「特定のデータ入力で起こる」「特定の時間帯に起こる」「処理データ量が一定を超えると起こる」など、ここで得られる情報の質が高ければ、問題に対してよりよい解決方法を選択することができます。

ひと昔前のエンタープライズシステムであれば、ユーザに影響が出ないよう休日や深夜に検証する」という方法がとられることもありましたが、インターネットに公開された現在の SaaS においては24時間365日の連続稼働が前提です。本番システムにデバッグ用のコードを仕込んで経過を見る、試行錯誤をするといった時間的な余裕がないこともあります。パフォーマンス問題が起きる前に、普段から運用するシステムの各種指標を収集できる体制を構築しておくことが大事です。

こういった道具立てを自分たちで全て用意するのは大変なので、外部の APM(Application Performance Management: アプリケーションパフォーマンス管理)サービスを使うのもひとつの選択肢です。

ボトルネックを特定する

パフォーマンス低下に影響を与えている要因のうち、最も問題視される箇所のことをボトルネックといいます。測定が終わったら、パフォーマンスを低下させている要因を列挙し、性能にもっとも大きな影響を与えているのが何なのかを明らかにします。「APIサーバのレスポンスが遅い」という問題ひとつとっても、パフォーマンス劣化の原因にはデータベースの検索クエリが遅い、外部API呼び出しのレスポンス待ちが長い、排他制御のロック解放待ちが長い…と、いろいろあるわけです。冒頭にも説明した通り、パフォーマンスチューニングは時間との戦いなので、複数の要因がある場合は最も大きな影響があるところから解決に着手していきます。パフォーマンス低下要因としてよく挙げられるN+1やフルテーブルスキャンが実際に起こっていたとしても、処理プロセス全体に占める割合が少ないのであればいったん後回しにする、という判断もありえます。

対応策を選ぶ

ボトルネックを特定したら、負荷の種類に合わせた対策を検討します。システムの負荷は CPU の計算速度に依存するもの(CPU bound) と、入出力に依存するもの(I/O bound) の2種類に大別されます。I/O は狭義にはディスクの読み書きを指しますが、広義には通信速度(ネットワークのI/O)なども含まれます。

CPU bound の負荷が問題になっている場合は「計算量を減らす」または「計算資源を追加する」アプローチで対策します。具体的には「効率の良いアルゴリズムを採用して計算量を減らす」「サーバ増設でCPUリソースを追加する」といった方法が取られます。

I/O bound の負荷が問題になっている場合は「入出力の総量を減らす」アプローチで対策します。こちらも「キャッシュを活用し、APIやクエリの呼び出し回数を減らす」「APIやクエリで出力するデータ量(HTTP(S)のレスポンスボディ、クエリの結果セット)を減らす」「インデックスを追加して検索のために走査するデータ量を減らす」「逐次処理の書き込みを一括登録に変更する」など、さまざまな方法があります。

終わりに

対応策が決まったら具体的な実装に落とし込み、効果を検証し、テストしてリリースという流れになります。このあたりも掘り下げると色々なネタがあるのですが、今回は「手を動かす前に考えてほしいこと」という形でいくつかのポイントを紹介してみました。手を動かすこれらを確認しておくことで対応の幅も広がりますし、完了時の報告もスムーズに行うことができます。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com