レベル0:物理デバイスの「絶望的な速度差」を知る
チューニングの目的は、一言で言えば 「遅いデバイスへのアクセスを減らし、速いデバイスで処理を完結させること」 です。
| デバイス | 速度(帯域・レイテンシ) | チューニングの主眼 |
|---|---|---|
| CPUキャッシュ | 爆速(ナノ秒) | データを「小さく」してここに詰め込む |
| メインメモリ | 高速(マイクロ秒) | インデックスをメモリ内に収める |
| SSD (NVMe) | 低速(メモリの1/100以下) | ランダムアクセスを避け、連続読み込みさせる |
| HDD / ネットワーク | 致命的に遅い | ここを叩いたら負け |
レベル1:クエリ・リライトと「型」の意識
まずはSQLそのものの不備を取り除きます。
- SELECT * の禁止:
- 不要なカラムの読み込みは、メモリ(バッファキャッシュ)の無駄遣いです。アプリケーションで使わないカラムは読み込まないようにしましょう。
- 暗黙の型変換の排除:
- 「設計が直感的でない(数値IDをVARCHARで持つ等)」ときにミスしやすい印象があります。DB側で暗黙的に型変換関数が走ってしまい、せっかくのインデックスが使われず、CPU負荷だけが跳ね上がります。インデックスには型変換関数が含まれていないためです。逆にファンクションインデックスというテクニックもあります。
- N+1問題の解決:
- 1回で済む結合を1,000回投げれば、ネットワーク遅延だけで数秒を失います。アプリケーションサーバーとデータベースサーバー間の往復を減らしましょう。
レベル2:インデックス戦略の深化
インデックスは「WHERE句を速くするもの」だけではありません。
- 複合インデックスの順序:
- 列の順番一つで効率が激変します。等価条件を前に、範囲条件を後ろにする「奥深さ」を理解する必要があります。
- カバリングインデックス:
- 意外と知られていないテクニックです。結合キーやSELECT対象をすべてインデックスに持たせることで、 「テーブル本体を見に行かない(Index Only Scan)」 という状態を作ります。物理I/Oを減らす意味で、その効果は計り知れません。私の経験上知っているアプリケーションエンジニアはごく少数で知っているエンジニアも後輩に指導していませんでした(なぜだ)。どうしようもないときに覚えていて損はないテクニックです。
レベル3:全体俯瞰とボトルネック特定
個別SQLの EXPLAIN(実行計画)も重要ですが、システム全体の「悲鳴」を聞くスキルが必要です。このあたりはデータベース管理者の範囲でしょうか。
- 全体分析ツール(STATSPACK / AWR / pg_stat_statements)の活用:
- インフラエンジニア時代、よくこれで「待機イベント」を見ていました。どのクエリがCPUを食い潰し、どのクエリがディスクI/Oで詰まっているかを俯瞰し、改善の優先順位を決めます。
- 「物理パワー」の誘惑を断つ:
- アプリ側は「インフラがどうにかしてくれる」と思いがちですが、アプリケーション遅延の原因の9割がSQLにあると言われており、どれだけメモリを積んでもキャッシュヒット率は上がらず、焼け石に水です。SQLのパフォーマンスチューニングを優先するようにしましょう。ちなみにデータベースサーバーのメモリサイジングがいい加減なプロジェクトだと本当に致命的になほどに物理メモリが少ないとか、物理メモリの空き容量があるのにデータベースで使われる設定になってないといったインフラエンジニアまたはデータベース管理者の明らかなミスも時折見かけます。
- ウィンドウ関数の活用
- 巨大なテーブルを自己結合するとパフォーマンス上問題が起きやすいです。SQLの実行計画を見ると分かりやすいですが、そうしたSQLはウィンドウ関数の利用で単純に考えればI/O負荷を半減させることが可能です。
レベル4:パーティショニング(水平分割)
データ量そのものが物理限界を超えた場合の定石です。
- 水平分割:
- 時系列などでデータを物理的な別ファイルに分けます。物理的にテーブルを分割することもありますし、データベースエンジンの機能で年単位などで論理的に分割することもあります。
- 効果:
- 全スキャンが必要な場合でも、特定のパーティションだけに絞り込む(Partition Pruning)ことで、読み込むデータ量を物理的に削減します。
レベル5:最終奥義・垂直分割とビューによる隠蔽
カバリングインデックスすら効かない(選択率10%程度以上だとテーブルフルスキャンが行われがちでI/Oが激重、CPU負荷が高い)場合の、文字通り「最終奥義」であり諸刃の剣です。本日のメインディッシュです。
- 手法: 結合キーとデータ部分の物理的切断:
- 巨大なカラム(長い文字列やバイナリなど)を持つテーブルを、 「結合キーのみの細いテーブル」と「データ本体」 に分割します。
なぜこれが最強なのか?
- データ密度の極大化:
- 1行を極限まで細くすることで、1ブロックに数千行が収まります。フルスキャンが必要なJOINでも、I/O量が劇的に減り、ハッシュテーブルがCPUのL3キャッシュに収まる可能性すら出てきます。
- ビューとトリガーによるカプセル化:
- テーブルをオーナーユーザーで分離し、アプリケーションには「ビュー」だけを見せます。更新はトリガーで制御することで分割したテーブル間の整合性を取ります。ただしトリガーによるオーバーヘッドがあり書き込み性能が低下するので実用に耐えうるか確認が必要です。これにより、アプリのコードは1文字も変えずに、裏側の物理構造だけを爆速化できます。
最後に
パフォーマンスチューニングは、アプリケーションエンジニアが論理を書き、インフラエンジニアが物理を支え、データベースエンジニアがその「橋渡し」となる構造を作る共同作業です。
「物理の力(スペックアップ)」で解決する前に、このロードマップに沿って 「物理法則を味方につける設計」 ができているか、一度立ち止まって考えてみてください。その先には、インデックス一つでは決して辿り着けない「爆速」の世界が待っています。