本番環境のPostgreSQLデプロイメントが、開発者が CREATE OR REPLACE FUNCTION を使用して既存の関数にオプション引数を追加したことにより、クラッシュしました。この変更によって同じ名前を持つ2つの関数が残ってしまい、データベースが「function is not unique(関数が一意ではありません)」を返し、APIが400エラーを出力しました。このインシデントは、たった一つのマイグレーションミスがいかにして稼働中のスキーマを密かに破損させるか、そしてなぜコードレベルのチェックだけでは不十分なのかを示しています。
何が起きたのか
チームは、ストアドプロシージャに新しいオプション引数を追加して拡張する必要がありました。彼らは、それが古い定義を上書きすると想定して CREATE OR REPLACE FUNCTION … を実行しました。しかし、PostgreSQLは引数リスト全体が完全に一致する場合にのみ、関数を置き換えます。シグネチャを変更すると、元の関数はそのままに、全く新しい関数のエントリが作成されます。
新しい引数にはデフォルト値が設定されていたため、以前と同じ数の引数を渡す呼び出し元は、どちらの定義にも一致する可能性がありました。PostgreSQLはどちらを呼び出すべきか判断できず、「function is not unique」エラーをスローし、それがAPIからの400レスポンスとして表面化しました。
コードリポジトリには単一の定義が表示されており、ソースツリーをスキャンするカスタムスクリプトも重複を報告しませんでした。重複はデータベース内にのみ存在しており、古いマイグレーションファイルがサーバー上で再実行された際に導入されたものでした。
なぜマイグレーションを見逃したのか
オプション引数を追加するマイグレーションは、単に CREATE OR REPLACE FUNCTION を実行するものでした。ロールバック後や再デプロイ時など、マイグレーションが2回目に実行された際、データベースはそのコマンドを「既存のものを置き換える」のではなく、「新しいオーバーロードを追加する」ものとして扱いました。マイグレーションは結果の状態を検証していなかったため、重複は気づかれないまま残り続けました。
リポジトリをチェックするスクリプトはソースファイルを検査しており、稼働中のスキーマを検査していませんでした。それは、裏口からバグが侵入している間に、表玄関を見ているようなものでした。
リスクの大きさ
たった一つの曖昧な関数が、それに依存するあらゆるサービスを停止させる可能性があります。修正には、関数数が正しくない場合にトランザクションをロールバックし、スキーマキャッシュにリロードを通知するプロセスが含まれました。
マイグレーションを保護する方法
チームは明示的なチェックを含むようにマイグレーションを再構築し、自己検証型の操作へと変更しました。
- トランザクションを開始する。これにより、失敗した場合には変更全体をロールバックできます。
- 新しいバージョンを作成する前に、古い関数を明示的に削除(DROP)する。これにより、定義が一つだけ存在することを保証します。
- 望ましいシグネチャで新しい関数を作成する。
pg_catalog内で指定された名前を持つ関数の数をカウントし、その数が正確に1であることを検証する。- カウントが異なる場合はトランザクションをロールバックし、重複が残るのを防ぐ。
- スキーマキャッシュにリロードを通知し、後続のクエリが更新された定義を参照できるようにする。
コードが正しいと決めつけるのではなく、データベースに「どのような関数が存在するか?」と問いかけることで、マイグレーションは繰り返しの実行、部分的なデプロイ、または手動の編集に対しても信頼できるものになります。
反論:利便性 vs 安全性
CREATE OR REPLACE FUNCTION は、個別の DROP 文を書かずに開発者が迅速にイテレーションを繰り返せるため、魅力的です。マイグレーションが一度だけ実行され、二度と実行されない環境であれば、このショートカットは問題なく機能します。リスクが生じるのは、テストデータベースをリセットする CI パイプライン、自動ロールバック、または本番環境での手動の再適用など、マイグレーションが再実行(リプレイ)される場合です。
まとめ
CREATE OR REPLACE を使用して関数のシグネチャを変更しても、置き換えが保証されるわけではありません。引数リストが異なる場合、PostgreSQL は密かにオーバーロードを作成します。マイグレーションに依存する本番環境では、ソースコードだけでなく、結果として得られるスキーマを検証する必要があります。明示的な DROP、トランザクションによるチェック、およびマイグレーション後のアサーションを組み込むことで、便利なショートカットを信頼性が高く再現可能なプロセスへと変えることができます。
