データレイアウトとアクセスパターンのベストプラクティス
データアクセス、特にデータ更新の特定のパターンは、データベースへの負荷を悪化させる可能性があります。可能であれば避けてください。
このドキュメントでは、避けるべきいくつかのパターンと、それに代わる推奨事項を示します。
高頻度の更新、特に同じ行への更新
多くのトランザクションによって同時に更新される単一のデータベース行は避けてください。
- 多くのプロセスが同じ行を同時に更新しようとすると、各トランザクションが行をロックして書き込みを行うため、待ち行列が発生します。このためトランザクションのタイミングが大幅に長くなり、Railsの接続プールが飽和してアプリケーション全体のダウンタイムにつながる可能性があります。
- 行の更新ごとに、PostgreSQLは新しいバージョンの行を挿入し、古いバージョンの行を削除します。高トラフィックシナリオでは、このアプローチはバキュームとWAL(write-ahead log)圧力の原因となり、データベース性能を低下させます。
このパターンは、集約が要求ごとに計算するにはコストがかかりすぎるため、実行中の集計をデータベースに保持する場合によく起こります。このような集計が必要な場合は、単一の行に実行中の合計を保持し、さらに個々の増分などの最近追加されたデータの小さな作業セットを保持することを検討してください:
- 新しいデータを追加する場合は、作業セットに追加します。これらの挿入はロック競合を引き起こしません。
- 集計を計算する場合、実行中の合計と作業セットからのライブ集計を組み合わせ、最新の結果を提供します。
- 作業セットを実行中の合計に組み込み、トランザクションでそれをクリアする定期的なジョブを追加し、リーダーが必要とする作業量を制限します。
広いテーブル
PostgreSQLは行を8KBのページにまとめ、一度に1ページずつオペレーションします。テーブル内の行の幅を最小にすることで、以下のことが改善されます:
- シーケンシャルスキャンとビットマップインデックススキャンの性能が向上します。
- バキューム性能:バキュームは各ページでより多くの行を処理できるため。
- (HOTでない)更新時には、各インデックスは行の更新ごとに更新されなければならないため、更新のパフォーマンス。
幅広のテーブルを軽減することは、データベースチームの100GBテーブル構想の一部です。
テーブルにカラムを追加する場合、テーブルの他のカラムと1対1の関係で、新しいカラムのデータに単独でアクセスするかどうかを検討します。もしそうであれば、新しいカラムは新しいテーブルに分割するのがよいでしょう。
すでにいくつかのテーブルがこの方法で分割されています。例えば
-
search_data
はissues
から分割されています。 -
project_pages_metadata
はprojects
から分割されています。 -
merge_request_diff_details
から分割されています。merge_request_diffs