HN 日本語サマリー

← 一覧へ戻る
プログラミング

Muhammad Aqeel: PostgresのUndo機能、一日の作業を失うことなく

Muhammad Aqeel: Undo for Postgres, without losing the rest of your day (pgedge.com)

17 pointsby hnisjafx405 コメント

要約

Postgresでは、誤ったSQL文を実行した場合の復旧に時間がかかり、専門知識を要する複雑な手順が必要です。この問題を解決するため、pgVolvraというツールが開発されました。pgVolvraは、テーブルごとの行変更を記録し、誤った変更のみを正確に元に戻す機能を提供します。

全文翻訳

PostgresのUndo機能、一日の作業を失うことなく

Postgresには、単一のステートメントを取り消す方法がありません。WHERE句なしでUPDATEを実行した場合、復旧はコマンドではなく手順となります。間違いが発生した時点のデータベースのコピーを復元し、損傷した行を抽出し、それ以降も動き続けているライブテーブルと手動で照合する必要があります。

この種の復旧は機能しますが、数時間かかり、最も経験豊富な担当者が必要で、テーブルがトラフィックを提供し続けている間に、あらゆるステップで事態を悪化させる可能性があります。悪いSQLの一秒が、最も手が回らない人々による午後の慎重な作業に変わります。

OracleユーザーはまさにこのケースのためにFlashbackを持っており、それは実際には2つの機能です。一つは過去を読み取る機能、もう一つはそれを元に戻す機能です。Postgresにはどちらもありません。ポイントインタイムリカバリは、その手順を可能にしますが、クラスター全体を復元するのであって、ステートメント単位ではありません。本番環境自体を巻き戻すと間違い以降のすべてが破棄されるため、本番環境と並行して復元し比較します。私はもっと小さく、より正確なものを求めていたので、pgVolvraを構築しました。

pgVolvraの機能

pgVolvraは、選択したテーブル上のすべての行変更を記録します。各変更について、変更前の行と変更後の行、そして誰が、いつ、どのトランザクションで変更を加えたかを保持します。問題が発生した場合、その記録を使用して影響を受けた行を元に戻しますが、それ以外のものには一切触れません。

エンジン全体はプレーンSQLとPL/pgSQLでできています。コンパイルされた拡張機能、shared_preload_libraries、スーパーユーザー権限、サーバーのファイルシステムへのアクセスは一切必要ありません。これは初日から設計上の制約であり、ツールがマネージドPostgresで動作する理由です。

セットアップ

インストールは、データベースに対して1つのファイルを実行するだけです。psql、任意のSQLクライアント、またはプロバイダーのWebコンソールから動作します。

psql "$DATABASE_URL" -f sql/volvra.sql

次に、カバーしたいテーブルを選択します。pgVolvraは、これを行うまで何も記録せず、それ以前に発生したことも復元できないため、このステップが重要です。

SELECT volvra.enable('public.orders');

これでセットアップは完了です。この時点から、ordersに対するすべてのINSERT、UPDATE、DELETEが記録されます。

動作例

自分で作成できるテーブルでの完全なシーケンスを以下に示します。意図的に同じ間違いを犯し、それを元に戻します。

  1. テーブルを作成し、pgVolvraを有効にします。主キーが必要です。これはpgVolvraが後で行を見つける方法だからです。
CREATE TABLE orders (
  id       bigint PRIMARY KEY,
  customer text NOT NULL,
  status   text NOT NULL,
  total    numeric(10,2)
);

INSERT INTO orders VALUES
  (1, 'ayesha', 'shipped', 240.00),
  (2, 'omar',   'pending', 99.50),
  (3, 'lena',   'shipped', 18.75);

SELECT volvra.enable('public.orders');
  1. ミスを犯します。WHERE句がないため、1行ではなくすべてがヒットします。
UPDATE orders SET status = 'cancelled';
UPDATE 3
  1. 1行に何が起こったかを確認します。volvra.historyはテーブルと主キーをjsonbとして受け取り、その行のすべてのバージョンを表示します。
SELECT ts, actor, op, old_row -> 'status' AS was,
       new_row -> 'status' AS now
FROM   volvra.history('public.orders', '{"id": 1}');

         ts          | actor | op |    was    |     now
---------------------+-------+----+-----------+-------------
 2026-09-16 11:04:19 | api   | U  | "shipped" | "cancelled"
  1. Undoをプレビューします。この呼び出しは何も書き込みません。正確な計画を構築し、何も変更される前に読み取れるように返します。
SELECT * FROM volvra.preview_undo(
         target  => 'public.orders',
         from_ts => now() - interval '5 min');
  1. pgVolvraにクエリします。confirm => trueなしでは、関数は拒否し何も変更しません。これはテスト中に何度も私を救ってくれました。
SELECT * FROM volvra.undo(
         target  => 'public.orders',
         from_ts => now() - interval '5 min',
         confirm => true);

3つの注文は元に戻ります。もし顧客が過去5分間に本当に注文をキャンセルしていた場合、そのキャンセルはそのまま残ります。なぜなら、pgVolvraはスナップショットをテーブルに復元するのではなく、記録された変更を逆にたどるからです。

タイムトラベルと早期警告

Undoは、何が間違っていたかを知ったときに手を伸ばすものです。それよりも前の段階に2つのものがあります。

1つ目は、触れることなく過去を読むことです。volvra.as_ofは、任意の時点でのカバードテーブルを再構築し、何も書き込みません。

SELECT * FROM volvra.as_of('public.orders', '2026-09-16 15:39');

これが最初に本当に必要なものです。何かを元に戻すかどうかを決定する前に、テーブルがどのように見えたかを確認し、現在の状態と比較したいはずです。その時点以降に削除された行は戻り、それ以降に作成された行は存在せず、ライブテーブルはそのままです。これはFlashbackの読み取り専用部分であり、何が起こったのかを把握している間は安全に実行できます。

2つ目は、16:10ではなく15:40に知らされることです。pgVolvraは、予想以上に多くの行を変更したステートメントがあった場合に警告できます。

SELECT volvra.set_warn_changed_rows(1000);

UPDATE orders SET status = 'cancelled';
WARNING:  volvra: one statement changed more than 1000 row(s) of public.orders

これが機能のすべてです。何もブロックせず、ステートメントを遅くするほどの影響もありません。WHERE句がないSQLが実行された瞬間に、顧客からの電話で発見するのではなく、それを声に出して伝えます。

警告はデフォルトでオフになっており、オフということはトリガーが全くアタッチされていないことを意味します。そのため、それを使用しないデータベースはコストを一切負担しません。

問題を実際に説明する言葉で表現する

時間ウィンドウは、大まかな選択肢です。ほとんどの場合、何が間違っていたかについて、より具体的なことがわかっています。pgVolvraはそれを直接言うことができます。

  • volvra.undo_txid(...)は、それが触れるすべてのテーブルで1つのトランザクションを逆にします。これは、後悔するマイグレーションです。
  • actorとdb_userは、1つのアプリケーションIDまたは1つのデータベースロールの作業を元に戻します。これは、他のすべてが正常に実行されている間に問題が発生したインポートジョブです。
  • volvra.mark('before-deploy')は、ある時点に名前を付け、volvra.undo_to('before-deploy')はそれに戻ります。
  • predicateは、条件に一致する行にUndoを絞り込み、tablesは複数のテーブルを1つの計画でカバーします。

pgVolvraは、各変更に対して1つではなく2つのIDを記録します。データベースロールと、接続が設定するアプリケーションレベルのアクターがあります。多数のサービスが単一のPostgresログインを共有するシステムでは、ロールだけでは有用な情報は何もわかりません。

安全規則

Undoは書き込みであり、不注意な書き込みは元の間違いよりも大きな損害を与えます。これは私が最も時間をかけた部分です。

15:40に発生した間違いが16:10に気づかれたとします。その30分間に、実際のユーザーが意図的に同じ行の一部を変更しました。古い値を盲目的に書き戻すと、あなたの作業を修復する間に彼らの作業を破壊してしまいます。

そのため、pgVolvraは各行に触れる前にチェックします。行がその時点の値を持っていない場合、その行は移動したと見なされ、操作全体が停止し、邪魔になっている行を通知します。skip_conflictsを渡すことで、残りを修正し、それらの行はそのままにしておくことができます。それらを上書きするように要求することはできません。強制フラグはなく、意図的に削除しました。なぜなら、強制フラグは最終的に常に使用され、常に最悪の日に使用されるからです。

また、1つの操作が触れることができる行数には制限があります。フィルターが予想よりもはるかに多くの行に一致した場合、続行せずに停止します。

ヒストリーはどこに保存されるか

デフォルトでは、pgVolvraはヒストリーを独自のデータベース内に、通常のテーブルとして保持します。これはシンプルで、追加のものは何も必要とせず、ほとんどの人が使用するものです。また、ヒストリーはデータベースと同じ運命を共有することを意味します。データベースを失うと、ヒストリーも失われます。

そのため、2番目のオプションがあります。小さなコンパニオンプロセスがデータベースの外で実行され、Postgresがコミットする各変更を読み取り、所有するストレージに改行区切りJSONとして書き込み、SHA-256ハッシュのマニフェストを付けます。これらのファイルは、pgVolvraやPostgresなしで読み取ることができます。

[画像: Covered tables record row changes through triggers into volvra.change_log and through the write-ahead log into companion JSON archives.]

pgVolvraは、トリガーを通じてデータベースに、またはコンパニオンティアを通じて外部アーカイブに変更を記録します。

どちらのティアも互いを必要としません。同じテーブルを監視し、異なる場所に書き込みます。どちらか一方を単独で実行することもできます。

コンパニオンの起動は1つのコマンドで完了します。レプリケーションスロットが欠落している場合は作成し、アーカイブ済みの最後のセグメントから再開し、継続的にストリーミングします。

volvra-companion run --dsn "$DATABASE_URL" --archive /srv/volvra-archive

リストア後の数時間の回復

ヒストリーがデータベースの外に保存されるようになると、別のことが可能になります。これが私が最も満足している機能です。

データベース自体が失われ、バックアップが唯一の方法となる場合があります。バックアップは02:00のもので、現在は15:40です。リストアは正しいデータベースを提供しますが、ほぼ14時間の作業が失われています。pgVolvraは、02:00以降にアーカイブした変更を読み取り、リストアが提供したものの上に再度適用できます。

[画像: Backup at 02:00, database loss at 15:40, and replay recovering the missing 13 hours and 40 minutes of work.]

バックアップの復元は時間を巻き戻し、リプレイはアーカイブされた変更を15:40まで進めます。

リストアはあなたを過去に戻します。リプレイはアーカイブされたヒストリーを、リストアが提供したものの上に再び進めます。

リプレイは、Undoと同じ仕組みを使用し、逆方向に向けられます。同じプランビルダー、同じ競合チェック、同じ行制限、同じ単一トランザクション。私はこれほど危険なものに2番目のコードパスを必要としませんでした。

また、私の言葉だけで出荷したくなかったので、テストは実際に行います。実際のpg_dumpを取得し、その上にさらにデータを書き込み、実際のDROP DATABASEを実行し、バックアップを復元し、アーカイブを復元し、リプレイを実行し、その後、両側のすべてのカバードテーブルのすべての行のハッシュを計算します。復元されたデータベースは、Postgres 14から19まで、失われたものと同一になります。2回目のリプレイは何も変更しません。

マネージドPostgresでの実行

これが私が最も気にかけている部分です。なぜなら、設計上の制約が報われるからです。今日のPostgresのほとんどはレンタルされており、レンタルPostgresでは拡張機能をインストールできず、スーパーユーザーになれず、サーバーのファイルシステムに近づくこともできません。

pgVolvraはこれらのいずれも必要としません。データシートから互換性を主張するのではなく、各プロバイダーでライブインスタンスに対して同じ24のチェック検証を実行しました。

SupabaseとNeonはどちらも無料プランでパスします。Supabaseは、独自のリアルタイム機能のために既にwal_levelをlogicalとして実行しており、postgresロールがレプリケーションスロットを作成できるため、設定は一切必要ありませんでした。Neonでは、コンソールで論理レプリケーションを一度有効にすると、その変更はプロジェクトで元に戻すことができないため、まずUndo側を確認し、アーカイブが必要な場合にのみオンにしてください。pgEdge Starfleetでは、スロット作成以外はすべて機能しますが、管理者からのALTER ROLE admin REPLICATIONが1つ必要です。

Amazon RDS、Aurora、Google Cloud SQLも同様に機能するはずですが、まだ検証が実行されていないため、ここでは主張していません。

コスト

ヒストリーの書き込みは無料ではないため、オーバーヘッドは最小限であるという主張ではなく、数値を以下に示します。

  • ステートメントによっては、書き込みスループットが11%から41%低下します。狭いUPDATEが最悪のケースです。単純なINSERTが最も安価で、11%から12%です。
  • 記録された各変更は約300〜400バイトかかります。
  • Write-ahead logのボリュームは約2.2倍になります。
  • 何も変更しない更新は検出されてスキップされ、デフォルトでは実際に変更された列のみが保存されます。

これが、すべてのテーブルをカバーするのが間違った考え方である理由です。データが失われると本当に困るテーブルだけをカバーし、残りはそのままにしておいてください。

pgVolvraがしないこと

  • バックアップではなく、バックアップに取って代わるものでもありません。バックアップは保持してください。
  • テーブルをカバーした後に加えられた変更についてのみ知っています。それ以前のことは何も復元できません。
  • リプレイは行を復元します。スキーマ変更、シーケンス、またはカバーしなかったテーブルをリプレイするわけではなく、ポイントインタイムリカバリでもありません。
  • 主キーのないテーブルはカバーできません。後でpgVolvraが行を見つける方法だからです。一意インデックスは代わりに使用されません。

主要なテイクアウェイ

  • 今日の悪いステートメントを修正するには、コピーを復元し、プレッシャーの下で手動で行を照合する必要があります。pgVolvraは、間違いが触れた行のみを、1つのステートメントで元に戻します。
  • pgVolvraは、拡張機能やスーパーユーザーなしで1つのSQLファイルとしてインストールされるため、マネージドPostgresで動作します。データシートから想定するのではなく、Supabase、Neon、pgEdge Starfleetで検証しました。
  • Undoは、間違いの後に変更された行を上書きすることを拒否し、意図的に強制する方法はありません。
  • コンパニオンアーカイブを使用すると、ヒストリーはデータベースよりも長生きし、リプレイは復元されたバックアップを、リストアが失った数時間分進めることができます。
  • セットアップがすべての作業です。今日テーブルをカバーすれば、ツールは必要になった日に役立ちます。それより前ではありません。

pgVolvra 1.0.0-beta2がリリースされました。Postgres 14から19をサポートし、PostgreSQLライセンスの下でオープンソースです。コード、ドキュメント、テストスイートはすべてgithub.com/pgEdge/pgvolvraにあります。ベータ版であり、どこで壊れるかの報告や質問は、リポジトリで歓迎します。