データがめっちゃ増えてくると(会社のチャットで締め切りメッセージが溢れるみたいに)、SELECTや集計クエリが遅くなってくるんだ。主な理由はこれ:
- インデックスがない。PostgreSQLがクエリ実行のためにテーブル全体をスキャンしなきゃいけない時(これを"Seq Scan"=シーケンシャルスキャンって言う)、クエリがかなり遅くなるよ。
- 非効率なSQLクエリ。クエリが最適化されてないと、インデックスがあってもパフォーマンス問題にぶつかる。例えば
WHEREでキー条件を忘れたら?実行時間がめっちゃ長くなるよ。 - 1つのテーブルに大量データ。例えば全年度の売上を一気に分析しようとすると、インデックスだけじゃ救えないこともある。
でも心配しなくてOK!この問題には2つの定番テクがある:インデックスとパーティショニング。
クエリ高速化のためのインデックス利用
インデックス作成のシンプルな例を見てみよう:
CREATE INDEX idx_sales_date ON sales(transaction_date);
- ここで
idx_sales_dateはインデックス名(好きな名前でOKだけど、意味が分かる名前がベター)。 ON sales(transaction_date)は、どのテーブルのどのカラムにインデックスを作るか指定してる。
このインデックスは、transaction_dateでよくフィルタする時に特に便利!
このインデックスで速くなるクエリ例:
SELECT *
FROM sales
WHERE transaction_date BETWEEN '2023-01-01' AND '2023-12-31';
複合キーのインデックス
もしクエリで複数カラム(例えばregionとproduct_id)をよく使うなら、複合インデックスを作るのもアリ:
CREATE INDEX idx_sales_region_product ON sales(region, product_id);
こういうクエリがめっちゃ速くなるよ:
SELECT *
FROM sales
WHERE region = 'North America' AND product_id = 42;
ユニークインデックスの利用
ユニークインデックスは検索を速くするだけじゃなく、カラムの値のユニーク性も保証してくれる。例えば:
CREATE UNIQUE INDEX idx_unique_customer_email ON customers(email);
これで同じメールアドレスの顧客をうっかり2人登録しちゃうことがなくなる。
分析関数向けのインデックス
SUM、COUNT、AVGみたいな分析関数も、インデックスを使うことで集計が速くなることがある。例:
CREATE INDEX idx_sales_amount ON sales(amount);
クエリ:
SELECT SUM(amount)
FROM sales
WHERE transaction_date >= '2023-01-01';
インデックスのおかげでこのクエリも速くなる。
大量データ対応のためのテーブルパーティショニング
テーブルパーティショニングは、大きなテーブルを論理的に小さいパーツ(パーティション)に分けること。例えばsalesテーブルを年度ごとに分けて、sales_2021、sales_2022みたいにできる。
難しそう?実はPostgreSQLなら意外とカンタン!
パーティショニングの種類
- レンジパーティショニング(
Range Partitioning)。例えば日付で範囲ごとに分ける。 - リストパーティショニング(
List Partitioning)。例えば地域ごとに値で分ける。 - ハッシュパーティショニング(
Hash Partitioning)。ハッシュ関数で分ける(手動で使うことは少なめ)。
パーティションテーブルの作成
じゃあ、年度ごとにパーティションする売上テーブルを作ってみよう。
CREATE TABLE sales (
id SERIAL PRIMARY KEY,
transaction_date DATE NOT NULL,
amount NUMERIC,
region TEXT
) PARTITION BY RANGE (transaction_date);
次に、各年度用のパーティションを作成:
CREATE TABLE sales_2021 PARTITION OF sales
FOR VALUES FROM ('2021-01-01') TO ('2022-01-01');
CREATE TABLE sales_2022 PARTITION OF sales
FOR VALUES FROM ('2022-01-01') TO ('2023-01-01');
日付でフィルタするクエリは自動的に必要なパーティションだけ使う。EXPLAINコマンドで確認できるよ。
パーティショニングの例
2021年だけの売上合計を出すクエリはこんな感じ:
SELECT SUM(amount)
FROM sales
WHERE transaction_date BETWEEN '2021-01-01' AND '2021-12-31';
見ての通り、PostgreSQLはsales_2021パーティションだけ見て、全テーブルをスキャンしない。
例:地域ごとのメトリクス集計の最適化
例えば、地域ごとの売上合計を計算したいとする。インデックスもパーティションもなければ超遅い。まずregionカラムにインデックスを作ろう:
CREATE INDEX idx_sales_region ON sales(region);
クエリ:
SELECT region, SUM(amount)
FROM sales
GROUP BY region;
インデックスのおかげで処理が速くなる!
例:時系列データのパーティショニング
トランザクションやログみたいな時系列データは、月ごとにパーティションを作るのがオススメ。例えば:
CREATE TABLE sales_monthly PARTITION BY RANGE (transaction_date);
CREATE TABLE sales_jan_2023 PARTITION OF sales_monthly
FOR VALUES FROM ('2023-01-01') TO ('2023-02-01');
クエリ:
SELECT SUM(amount)
FROM sales_monthly
WHERE transaction_date >= '2023-01-01' AND transaction_date < '2023-02-01';
PostgreSQLはsales_jan_2023パーティションだけ読むから、速い!
例:インデックスとパーティショニングの組み合わせ
インデックスとパーティショニングは組み合わせて使える。例えば各パーティション内にインデックスを作ることもできる。例:
CREATE INDEX idx_sales_amount_jan_2023 ON sales_jan_2023(amount);
よくあるミスを避けるには
パフォーマンス問題の多くは、インデックスやパーティショニングの使い方ミスが原因。例えば:
- インデックスが多すぎるとINSERTが遅くなる。
- パーティションは均等にデータが入るように設計しよう。小さすぎても大きすぎてもパフォーマンスが落ちる。
- 最適化前にパフォーマンス分析(
EXPLAIN ANALYZE)を忘れるのは、車を修理するのにボンネットを開けないのと同じ。
最適化で本当に速くなってるか、ちゃんとチェックしよう!色々試してみるのも大事だよ。
GO TO FULL VERSION