【実録】PostgreSQLが過呼吸に! 〜インデックスがないDBは、巨大迷路でウォーリーを探すようなもの〜
先日、本番環境のDBが「ウッ……ガハッ……!」と苦悶の表情(CPU使用率100%)を浮かべていたので、緊急オペを行いました。
その原因は、SQL界の「ウォーリーをさがせ」状態にありました。
1. 現場の惨状:pg_stat_activityの告白
まずは状況把握。pg_stat_activity という「DBの覗き窓」から、今何が起きているのかを見てみます。
SELECT pid, state, query FROM pg_stat_activity WHERE state != 'idle';
すると、そこには**「地獄の底から湧き出てきたような長文クエリ」**が居座っていました。
pid | state | query
-----+--------+-------------------------------------------------------------
615 | active | WITH pgrst_source AS (
| SELECT "customers".*,
| COALESCE(json_agg("orders"), '[]') AS "orders",
| -- (以下、数千文字に及ぶJOINとサブクエリの嵐)
この PID: 615 くん、何をしているかというと: 「顧客(customers)」を呼び出し、その顧客の「注文(orders)」を全件探し、さらにその注文に紐づく「注文商品(items)」や「配送先(destinations)」を……と、芋づる式にデータを探し回っていたのです。
2. なぜDBは死にかけていたのか?
今回の問題は、テーブル間の「つながり」に**索引(Index)**がなかったことです。
イメージしてください。あなたは巨大なAmazonの倉庫に立たされています。 上司から**「顧客ID:99番さんの、過去の注文履歴を全部持ってきて!」**と言われました。
- インデックスがある場合: 「注文管理台帳」の「顧客ID順」のページをパッと開き、「99番さんは……あ、この5件ね」と1秒で終了。
- インデックスがない場合(今回のケース): **「全在庫の段ボールを一つずつ開けて、顧客IDが99番かどうかを確認する」**という狂気の作業が始まります。
これを orders、items、destinations と全テーブルで繰り返していたのですから、DBが過呼吸になるのも無理はありません。
3. 執刀:3本のインデックスという「地図」
迷えるDBくんに、私は3枚の地図を授けました。
-- 1. 「どの注文がどの顧客のものか」を即座に引ける地図
CREATE INDEX ON orders(customer_id);
-- 2. 「どの商品明細がどの注文のものか」を即座に引ける地図
CREATE INDEX ON order_items(order_id);
-- 3. 「どの配送先がどの注文のものか」を即座に引ける地図
CREATE INDEX ON shipping_destinations(order_id);
4. 奇跡の復活
インデックスを貼った瞬間、世界が変わりました。
- Before: 「えーっと、顧客ID 99番の注文は……1箱目……違う……2箱目……違う……(数分経過)」
- After: 「顧客99番? 3番棚の左から5番目にあるよ!(0.01秒)」
CPU使用率は一気に静まり返り、DBは平穏を取り戻しました。
まとめ:そのJOIN、地図はありますか?
PostgRESTなどのツールを使っていると、非常に便利で複雑なクエリが自動生成されます。しかし、その裏側でDBが「フルスキャン(全件しらみつぶし)」という地獄の作業を強いられていないか、時々覗いてあげてください。
「外部キー(ForeignKey)を貼ったら、セットでインデックスも貼る」
この鉄則を守るだけで、救われるDBの命があります。現場からは以上です。