プロトタイプ開発のスピードを守りながら、将来のパフォーマンス問題を防ぐ方法

みなさん、こんにちは。

先日、PostgreSQLを使っているシステムのバージョンアップ作業中に、ちょっとした不具合修正を行いました。内容はこんな感じです。

「データ件数の増加に伴い、メイン画面や管理画面のクエリ実行時間が増大。タイムアウトが発生する問題を修正」

調べてみると、原因はいたって当然のことでした。sensor_log という膨大な時系列テーブルに対して、インデックスがない状態で複雑なJOINをしていたこと、そしてアプリケーション側で「N+1問題」が発生していたことです。

開発初期の少量のデータではサクサク動いていたので、問題が表面化しなかったんですね。実際の運用でデータが積み上がってから悲鳴を上げる……という、典型的なケースでした。

この経験から、「スピードを落とさずに、どうやって将来の爆弾を防ぐか」について、私なりの教訓をまとめてみたいと思います。

 


 

なぜ開発時に気づけなかったのか

 

このシステムのプロトタイプは「とにかく早く動くものを!」という方針で開発されました。

これ自体は、ビジネスとして非常に正しい判断です。仮説検証のためのプロトタイプに、最初から本番級のガチガチなパフォーマンス設計を求めるのは、正直「過剰投資」になってしまいます。

ただ、今回の問題点は「プロトタイプで妥協した箇所」がどこにも記録されていなかったことにありました。

開発した本人の頭の中には「ここは一旦逃げたな」という自覚があったはずです。でも、それがコードにもドキュメントにも残っていなかった。結果として、プロトタイプがそのまま「正式なプロダクト」として育ってしまったのが盲点でした。

 


 

開発時からパフォーマンスを意識するための4つの習慣

 

もし「初期段階からもう少しだけ意識しておきたい」という場合は、以下の手法が有効です。

1. 本番相当のデータ量を用意してみる

これが一番確実です。少量のデータだと、データベースはインデックスを使わずに全件走査(Seq Scan)した方が速いと判断してしまいます。特に時系列データのように増え続けるテーブルは、早い段階でダミーデータをドバッと入れてテストするのがおすすめです。

 

2. 「EXPLAIN ANALYZE」をクセにする

クエリを書いたら、一度実行計画を覗いてみましょう。

EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT ... -- 開発中のクエリ

見るべきポイントは3つだけです。

  • 大きなテーブルで Seq Scan(全件スキャン)が出ていないか?
  • 推定行数と実際の行数が大きくズレていないか?
  • 実行時間(actual time)が許容範囲内か?

 

3. スロークエリログを味方につける

postgresql.conf で設定を変えておくと、遅いクエリを勝手に教えてくれます。

log_min_duration_statement = 1000  -- 1秒以上かかったらログに出す

開発中からこれを出しておくと、無意識に書いた重いクエリにすぐ気づけます。

 

4. N+1問題をコードレビューで捕まえる

「ループの中でデータベースに問い合わせていないか?」

「集計処理を無理やりアプリ側のメモリでやっていないか?」

これを確認するだけで、リリース後のトラブルは激減します。

 


 

スピードを殺さない「現実的な」アプローチ

 

とはいえ、毎回EXPLAINをかけるのは大変ですよね。そこで、もっと低コストで将来に備える方法を提案します。

 

「後から直せるもの」を見極める

まず、全てを完璧にする必要はありません。以下の表のように切り分けて考えましょう。

後から比較的ラクに直せる後から直すのがめちゃくちゃ大変
インデックスの追加テーブル設計・カラム定義の変更
クエリの書き換えデータの正規化・構造の見直し
DBサーバの設定変更外部キー制約の有無
N+1問題の解消データ型の選択ミス

インデックスやクエリは、後からでも「力技」でなんとかなります。でも、スキーマ設計(テーブルの形)だけは、プロトタイプ段階から丁寧に行うのが、結局一番コスパが良いんです。

 

「負債メモ」を残す勇気

コードの中に、一言 // TODO を残すだけで世界が変わります。

// TODO: sensor_logは月100万件ペースで増える想定。将来的にインデックス追加が必要
List<DashboardItem> list = dashboardRepository.fetchFilteredItems(condition);

完璧に設計する時間はなくても、「どこを妥協したか」を可視化しておくだけで、後から対処するコストは劇的に下がります。

 

ドキュメントに「データ量の想定」を1行書く

設計の片隅に、これだけ書いておきましょう。

sensor_log: 1施設あたり月100万件増加を想定

この1行があるだけで、次にそのコードを見る人の意識がガラッと変わります。

 


 

経験の浅いメンバーと一緒に開発するときは

 

プロトタイプ開発を若手や経験の浅いメンバーに任せる場合は、さらに注意が必要です。彼らは「逃げた」という自覚すらないまま、爆弾を埋め込んでしまうことがあるからです。

特に以下の4点は、仕組みとしてチェックするようにしましょう。

  • SELECT * を使わない
    通信量やメモリを無駄に食いつぶします。
  • ループ内のDBアクセス(N+1)
    デバイスが100台あれば100回クエリが飛ぶ……という恐ろしいことが起きます。
  • インデックスの概念を伝える
    外部キーにはとりあえず貼っておく、くらいのルールでも効果があります。
  • ページングを実装する
    「今はデータが少ないから」と全件取得(findAll)にしてしまうと、将来確実にタイムアウトします。

これらを個人のスキルに頼るのではなく、「レビューのチェックリスト」や「SQLを書く前に相談する文化」といった仕組みでカバーするのが、チームとしての正解だと私は思います。

 


 

妥協の記録が大事

 

今回の教訓を一言でいうなら、これに尽きます。

完璧な設計を目指すより、「どこを妥協したか」を記録する文化を作ろう

プロトタイプのスピードを維持しつつ、将来の破綻を防ぐ「最安」の対策は、コードではなく「メモ」で負債を可視化しておくことです。

これは人間同士のコミュニケーションはもちろん、生成AIを活用した開発においても、指示(コンテキスト)として非常に有用です。AIに「ここは将来的にこうしたい箇所だ」と伝える手がかりになりますからね。

「スピード重視」と「品質への誠実さ」を両立させるために、ぜひ皆で心がけていきましょう!

 

本日も最後までお読みいただき、ありがとうございました。

それでは、よいシステム開発を!

カテゴリ: プロジェクト管理

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール