23 ポイント 投稿者 GN⁺ 2026-01-23 | 4件のコメント | WhatsAppで共有
  • データ復旧や規制遵守のために archived_at 列ベースのソフトデリート がよく使われるが、時間が経つにつれて 複雑さと非効率 が増していく
  • この方式は クエリ、インデックス、マイグレーション、復元ロジック を複雑にし、ほとんどのアーカイブデータは再び読まれないため データベースに不要な負荷 をもたらす
  • 代替案として アプリケーションイベントベースのアーカイブトリガーベースのアーカイブWAL(Change Data Capture) ベースのアーカイブ が提示されている
  • 各方式は 運用の複雑さ、インフラ要件、復元のしやすさ に違いがあり、特に WAL ベースは Kafka など外部システムとの統合 が必要になる
  • 新しいプロジェクトであれば トリガーベースのアプローチシンプルさと保守性の面で最もバランスの取れた選択 である

ソフトデリートの問題点

  • 一般的には deleted ブール値や archived_at タイムスタンプ列を使ってデータを論理的に削除する
    • 顧客が誤ってデータを削除したときに復旧できる
    • 規制や監査の目的で保持が必要な場合もある
  • しかし archived_at 列は クエリ、運用、アプリケーションコード 全体に複雑さをもたらす
    • ほとんどのアーカイブデータは再度読まれない
    • API の動作問題や自動化ツール(Terraform など)によって 数百万件の不要な行 が蓄積する可能性がある
  • アーカイブデータの整理作業が設定されていないと データベースのバックアップおよび復元時に性能低下 が発生する
  • クエリやインデックスでアーカイブデータをフィルタリングする必要があり、データ漏えいのリスク が存在する
  • マイグレーション時に古いデータの処理やデフォルト値の修正が難しい
  • 復元ロジックが複雑になり、外部システム呼び出し が必要な場合はバグが発生しうる
  • 結果として archived_at 方式はシンプルに見えても 長期的には保守コストが高い

アプリケーションレベルのアーカイブ

  • 削除時に イベントを発行 し、それを SQS に送って別のサービスが S3 にアーカイブする
  • 利点
    • 主要データベースとアプリケーションコードをシンプルにできる
    • 外部リソースのクリーンアップを 非同期処理 にして性能と安定性を向上できる
    • JSON 形式でシリアライズして アプリケーションに適した構造 で保存できる
  • 欠点
    • アプリケーションコードのバグによって アーカイブデータが失われる 可能性がある
    • メッセージキューなど 運用インフラの複雑さが増す
    • S3 のアーカイブデータには 検索および復元ツールが必要

トリガーベースのアーカイブ

  • 削除前トリガーが行を 別の archive テーブル に JSON 形式でコピーする
    • 例のテーブル: archive(id, table_name, record_id, data, archived_at, caused_by_table, caused_by_id)
  • 外部キー削除(cascade)時の 削除原因の追跡 のためにセッション変数(archive.cause_table, archive.cause_id)を使う
    • どの親レコードが子データを削除したのか確認できる
  • 利点
    • ライブテーブルをクリーンに保てる ため、archived_at 列が不要
    • アーカイブテーブルの整理(WHERE archived_at < NOW() - INTERVAL '90 days')が簡単
    • クエリとインデックスの効率を維持でき、マイグレーションも単純化される
    • バックアップサイズを削減できる
  • アーカイブテーブルは 別テーブルスペース時間パーティショニング で管理できる

WAL(Change Data Capture) ベースのアーカイブ

  • PostgreSQL の WAL ログを読んで削除イベントを外部システムへストリーミング する
    • 代表的なツール: Debezium(Kafka と連携)
    • 経路の例: PostgreSQL → Debezium → Kafka → Consumer → Archive Storage
  • 軽量な代替案
    • pgstream: WAL を webhook やメッセージキューへ直接送信
    • wal2json: WAL を JSON として出力
    • pg_recvlogical: PostgreSQL 組み込みの論理レプリケーションツール
  • 運用の複雑さ
    • Kafka ベースは 監視・障害対応・チューニング が必要
    • コンシューマーが遅延すると WAL ファイルが蓄積 → ディスク容量不足のリスク がある
    • PostgreSQL 13+ の max_slot_wal_keep_size 設定で制限可能
    • レプリケーションスロットの遅延監視とアラートが必須
  • 利点
    • アプリケーションコードを変更せずに すべての変更をキャプチャ可能
    • 多様な宛先(S3、データウェアハウス、検索インデックス) へストリーミングできる
    • 基本データベースに追加負荷がない
  • 欠点
    • 運用の複雑さとインフラコスト が高い
    • コンシューマー遅延時に データ損失または再同期が必要 になる可能性がある
    • スキーマ変更時に ソースとコンシューマー間の調整が必要

削除を処理しないレプリカというアイデア

  • DELETE クエリを無視する PostgreSQL レプリカ を維持するアイデアが提示されている
    • 削除されていないすべてのデータを蓄積して保持できる
    • アーカイブデータを 直接クエリ可能
  • 潜在的な問題
    • 削除情報を区別できない可能性がある
    • マイグレーション適用時に衝突のリスクがある
    • ストレージ容量と運用コストが増加 する

結論

  • 新しいプロジェクトでは トリガーベースのアーカイブ方式 が最も実用的な選択
    • 設定が簡単で、ライブテーブルをクリーンに保てる
    • 別個のインフラがなくてもアーカイブデータの参照と管理がしやすい
  • 複雑なインフラがすでに存在する、または複数宛先へのストリーミングが必要な場合には WAL ベースのアプローチ が適している

4件のコメント

 
love7peace 2026-01-23

トリガーベースだとDBに負荷がかかると習った気がするけど……? トリガーを勧めるのか

 
nemorize 2026-01-24

その程度のトリガーで発生する負荷が問題になるなら、トリガーがなくてもすでに問題だらけの状況です

 
cherrycoder 2026-01-23

結局のところ、規制はいつだってコストですね。まあ、どうせ最終的には消費者が負担する分ですが。

 
GN⁺ 2026-01-23
Hacker Newsの意見
  • 私が働いている銀行ドメインでは、むしろ soft delete のほうが有利だと感じた deleted_at カラムがあればクエリの記述が明確になり、分析用クエリや管理者ページでも同じデータセットを扱える 削除はほとんどの場合まれで、soft delete された行が性能問題を起こしたこともほぼなかった また関係がそのまま維持されるので、**復元(undo)も簡単だ 私はさらに進めて、行を完全に不変(immutable)**にし、更新時には新しい行を追加する方式を好む ログを残すなら、DB トリガーを使って INSERT/UPDATE/DELETE 時に複製テーブルへ記録を残すアプローチがよいと思う

    • あなたの言うことは正しいが、それは削除がまれな場合に限ると思う 私が見たテーブルで 50〜70% が soft delete 済みだったケースでは、性能低下は確実にあった 結局 soft delete は状況次第で、事前分析が必要だ
    • Postgres では、soft delete されていないデータだけをインデックスするよう設定することもできる ほとんどの場合は不要だが、RAM 節約には役立つかもしれない
    • 銀行での soft delete は、監査可能性(auditability)の不足を一時的に覆い隠す解決策にすぎない 本当の解決策はEvent Sourcingで、すべての変更をイベントとして記録すべきだ 性能は落ちるが、**スナップショットと同期(sync)**で補える
    • DB を不変構造で運用するなら、Datomic のようなシステムを検討する価値がある time travel 機能により、過去の状態を完全に参照できる
    • 以前保険会社で働いていたときも、各テーブルをappend-only ログとして運用していた 最新状態は最も大きいタイムスタンプを持つ行にあり、過去状態はフィルタで参照できた この方式は強力な履歴管理を可能にする
  • soft delete の最大の落とし穴はクエリの複雑さだ 最初は WHERE deleted_at IS NULL を追加すればよいと思うが、数か月たつとフィルタ漏れで幽霊データがレポートに現れる View で解決できるが、結局は並列アクセスパターンを維持する必要があり、削除済みデータを参照するときは抽象化を迂回しなければならない Event sourcing のほうがよりクリーンだが、運用負荷が大きいため、たいていはハイブリッドなアプローチが選ばれる

    • View は十分に強力なツールだ 問題は、多くのSWE と BI エンジニアが SQL やスキーマ設計に慣れていないことだ soft delete よりもっとよくある問題は、Type 2 Slowly Changing Dimension の扱いだ たいていは不要にaudit tableを作って、非効率な UPDATE/INSERT を繰り返している 実際 DB は本当に美しいシステムなのに、それだけ十分に尊重されていないのが残念だ
  • soft delete がDB の組み込み機能として提供されるとよいと思う テーブル単位で有効化し、削除処理の戦略を選べるなら理想的だ

    • 実際にIceberg、Delta Lake、BigQueryのようなシステムはtime travel機能を提供している しかし多くのチームはカスタム要件のせいで、結局**SCD(Slowly Changing Dimension)**方式で実装している
  • 私の経験では、トリガーベースのアプローチが最も安定していた アーカイブテーブルはappend-onlyで維持し、復元はアプリケーションレイヤーで処理すべきだ 更新は soft delete と見なし、トリガーが以前の状態をキャプチャするようにする トリガーは必ずBEFOREのタイミングで実行されるべきで、ロジックは単純でなければならない パーティションは月単位が一般的で、書き込み負荷が高いなら日単位に分けるのがよい

  • 私は DB がstateful → statelessへ進化してほしいと思っている すべての変更を append-only イベントとして記録し、必要なデータはviewで表現する構造を好む DB が自動でmaterialized indexを管理してくれたら理想的だ 一部の最新 DB はこうした機能を提供しているが、まだOLTP 中心の発展が不足している

  • 以前いた会社では、すべてのシステムに soft delete を適用していた 教授も「ビジネスの世界ではデータは決して削除されない」と言っていたのを覚えている

    • 完全削除は将来のデータ分析能力を自ら制限する行為だ ストレージは安いのだから、データは決して消すべきではない
    • しかし更新については、教授が何も言っていなかったのが興味深い
  • データベースは**事実(fact)**を保存する場所だ レコードの作成は新しい事実であり、削除もまた別の事実だ しかし行を物理的に消すと、事実が失われる ほとんどの場合、このような削除は望ましくない

    • ただしデータが漏えいリスクを持つ資産なら、むしろ大規模削除が必要になるかもしれない 維持コストとセキュリティリスクを考慮すべきだ
    • DB が不変でないなら、更新そのものがすでに失われた事実を生み出していることになる データを永久保存するという判断は慎重であるべきだ
    • 個人的には、データストアは参照と挿入の 2 つの操作だけをサポートすべきだと思う そのためにはデータのライフサイクルを理解することが重要だ
  • Firezone では最初、soft delete を監査ログ用に使っていたが、マイグレーションの問題で断念した その代わり、Postgres CDC(Change Data Capture)を使って別の書き込み最適化テーブルへイベントを送る方式に切り替えた soft delete はユーザー復元機能には有用だが、監査やコンプライアンス用途には不適切だと思う

    • 単純なプロジェクトでは、DB の変更ではなくAPI 呼び出し自体を監査するほうがより効率的だ
  • soft delete フィールドを持つテーブルの上にViewを作り、削除済み行を隠す方式はきれいだ こうすればアプリケーションは削除の有無を気にする必要がない

    • Postgres の**RLS(Row Level Security)**を使えば、soft delete された行を自動で隠せる アプリケーションは引き続き同じテーブルに対して読み取り・書き込み・削除を行う
  • **スキーマドリフト(schema drift)**をどう処理するのか、という疑問がある 削除当時のスキーマでシリアライズされたデータを後で復元しようとすると、スキーマ変更が問題になる

    • 私の経験では、アーカイブされたオブジェクトにアクセスされることはほとんどない 削除後数日以内に復元するケースが大半なので、スキーマ変更の影響は小さい 古いアーカイブを新しいモデルへマイグレーションするのは複雑でエラーが起こりやすい作業だった 結局、システムの使われ方によってアプローチ戦略は変わる