1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DBのパフォーマンス改善

Last updated at Posted at 2025-08-19

概要

DBの read のパフォーマンスを改善したいとき、候補となる改善策をいくつか挙げたいと思います。
他の DB タイプへの移行(Amazon Redshift や Elasticsearch を使うとか)やテーブルスキーマの見直しなどは考えません。

候補

index

真っ先に思い浮かぶ選択肢で、最もシンプルで効果を発揮してくれるものです。

index が貼ってあるカラムを使った検索において、通常の探索よりも高速になります。index によって並び替えられた B-Tree を用意しておきます。検索時にはまず、B-Tree を探索して、該当するリーフノードを特定します。B-tree のリーフノードは、実データを持たず、実データへのポインタを持っているので、リーフノードを特定するだけでは、データの取得はできません。リーフノードが持つ実データへのポインタを使うことで、実データを取得します。この実データを取得する作業はディスクI/O です。

clustered index

物理的なデータ順序を利用している点で、通常の index と異なります。
リーフノードの特定までは同じです。そしで、リーフノードが持つ実データへのポインタを使って実データを取得するのも一緒なのですが、効率が異なります。clustered index において、実データは物理的に index の順番で並んでいます。なので、リーフノードが特定されたら、一回のディスクI/O でまとめてごっそり実データを取得可能です。一方、通常の index は、リーフノードが特定されたあとに、「リーフノード→実データ」のディスクI/O がデータの数だけ発生してしまうので、効率が悪いです。ディスクI/Oはパフォーマンス悪化の原因になりやすく、clustered index で大幅にパフォーマンスが大幅に改善することがあります。
物理的な順番なので、テーブル一つにつき一つの clustered index しか持てません。デフォルトでは PK が clustered index となっています。clustered index を PK 以外に貼ると、当然ながら PK の clustered index の恩恵が受けられなくなります。PK は頻繁に使われるカラムであり、clustered index が貼ってある効果は大きいと思うので、PK から clustered index をはがすときはしっかり検討する必要があります。集計のテーブルとかは、PK に貼ってあってもあまり意味がなかったりするので、別のカラムに貼ってもいいかもしれません。

index only scan (conveing index)

ディスクI/Oがパフォーマンスのボトルネックになり得る話をしましたが、index only scan はディスクI/O をゼロにする戦略です。取得したいカラムを全て index に含めます。こうすることで、リーフノードに到達した時点で、必要なデータがすべてある状態になります。なので、「リーフノード→実データ」のディスクI/O がなくなります。
この話を聞くと、conveing index にするために、index に含めるカラムを増やしたくなると思いますが、注意が必要です。そもそも、index は書き込みのパフォーマンスが落ちます。これは、convering index に限った話ではないですが、書き込みの負荷が高いのに、index を増やしすぎるのは注意しましょう。また、index が含むことのできるカラム数には上限があるので、それも気を付けましょう。

materialized view

集計テーブルを事前に作っておくイメージです。A テーブルと B テーブルと C テーブルを JOIN して...みたいな操作を毎回やってたら時間がかかるので、事前に集計したテーブルを作っておきます。materialized view は作成された時点でのデータを保持するので、リアルタイムで更新されていく実データとはずれてしまいます。なので、時々、refresh 操作が必要です。
materialized view ではなくて、普通に新しい集計テーブルを作ってしまうのも一つの方法です。集計には時間がかかるので、メインのテーブルの更新と同時にジョブを積んでおいて、非同期で集計テーブルを更新します。

partitioning

垂直パーティショニングと水平パーティショニングがあります。ほとんどの場合、水平パーティショニングです。水平パーティショニングは、ある基準に基づいて、テーブルを水平に分割します。例えば、一年ごとにテーブルを分割します。単純にレコード数を減らすことで、パフォーマンスを改善しようというものです。
(見たことないので、詳しくは知らない。すみません)

参考

追記

上記以外でパフォーマンス改善する方法を、DB 詳しい人にアドバイスもらったので紹介します。

DB

innodb_buffer_pool_size (postgres だと shared_buffers ?)

一般的に、データアクセスの際に都度データベースにアクセスするよりも、キャッシュから読み込む方が早いため、バッファプールを用意することがパフォーマンスの向上に繋がります。 しかし、この値を大きく設定しすぎると、サーバー本体の空きメモリが不足してパフォーマンスに問題が発生する可能性があるため、一概に大きい値に設定すれば良いというわけではありません。

上記で説明したディスク I/O のときに、ここで設定したメモリ量だけキャッシュを使ってくれます。
マシン全体のメモリ量を上げても、この設定を上げないと、実際に使用されるキャッシュは変わりません。

join_buffer_size (postgres だと work_mem ?)

単純インデックススキャン、範囲インデックススキャン、およびインデックスを使用しないため完全テーブルスキャンを実行する結合について、使用されるバッファーの最小サイズ。 通常の場合、高速な結合を得るための最適な方法は、インデックスを追加することです。 インデックスを追加できない場合、より高速な完全結合を得るために、join_buffer_size の値を大きくします。 2 つのテーブル間の完全結合 1 つに対して 1 つの結合バッファーが割り当てられます。 インデックスが使用されない複数テーブル間の複雑な結合については、複数の結合バッファーが必要になることもあります。

work_mem を上げすぎると、最大セッション数 * work_mem が合計のメモリを超えてしまい、OOMが起きる可能性があるので注意してください。

ソート処理とかで、work_mem を溢れて SWAP すると、メモリじゃなくてディスクでソードされるので、めちゃくちゃ遅くなることがあります。

クエリ

断片化の解消

PostgreSQLのインデックスは、データベースのテーブルに対する操作(INSERT、UPDATE、DELETEなど)によって徐々に大きくなることがあります。これをインデックスの肥大化と言います。肥大化したインデックスはディスクスペースを無駄に消費し、インデックスの検索性能を低下させる可能性があります。

統計情報収集

??

ヒント句

??

join や subquery を別のクエリに分けて実行することで、DBのCPUの占有時間やメモリ使用量を削減できるかも

subquery はオプティマイザが良くないと N 回実行するので、CTE(WITH) に切り出してみるといいかもしれません。また、このとき、CTE(WITH) は work_mem を消費するので、work_mem を溢れることが心配なら、完全に別クエリに切り出すことも考えるのがよいです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?