削除したデータが戻ってくる:キャッシュ・検索・バックアップまで消す設計
退会処理でDBを消しても、コピーとバックアップは残り得ます。TTLの期限判定、削除イベントの再送、復元後の再削除、大量削除の進め方を、4件のTipsから解説します。
削除したデータが戻るのは、コピーの範囲が違うから
退会した人のプロフィールが検索に残り、障害復旧後に再び表示される。対策は削除APIのSQLだけでは決まりません。この記事は、架空の会員サービスで削除要求の受付から、キャッシュ・検索・分析・外部連携・バックアップまで追う運用設計です。法的な消去期限の解説ではなく、合意した削除範囲を実装と検証へ落とします。
認証・認可した削除要求を記録 → 正本の利用を停止し削除イベントを永続ストレージへ保存 → コピーごとに削除を反映 → 保存先の結果と残存データを確認 → 未完了を再試行・照合して完了を記録
| 保存先 | 削除の方法の例 | 完了確認 |
|---|---|---|
| キャッシュ | 該当キーを無効化し、古い読取からの再投入を防ぐ | 削除後の取得と古い更新の再送を試す |
| 検索・分析 | 削除イベントや対象IDで反映 | IDによる照合と検索結果への反映を確認 |
| 外部連携 | 相手の削除APIと状態照会を使う | 受付と処理完了を区別する |
| バックアップ | 隔離・保持期間・復元後の再適用を設計 | 復元訓練で削除済みデータを再表示しない |
TTLの期限と、読めなくする時刻は別に設計する
DynamoDBの期限切れ項目は削除前なら読み取りに現れ得ます。QueryやScanの期限フィルタに加え、直接取得やキャッシュなど、すべての利用経路で有効性を判定します。このケースではnow >= expires_atなら無効とし、物理削除の完了を待ちません。期限を短くするだけでは、本来の有効期間と非同期削除の問題を混ぜてしまいます。
item = authorized_read(subject_id)
if item is missing or now >= item.expires_at:
deny_use()
else:
continue_with_authorized_operation(item)利用停止、オンラインのコピーからの削除、バックアップの保持終了を別の状態として記録します。「削除済み」という1つの表示で隠さず、どこまで終わったかを説明できる契約にします。保持期間は製品ごとの仕様・保存先・復旧要件に合わせ、単一のTTL設定から全コピーの消去時刻を推定しません。
削除イベントは再送・順序逆転・失敗まで扱う
正本を消した直後に処理が落ちても削除の出来事を失わないよう、OutboxやCDCなどで接続します。受信側は削除IDと対象の世代を使って同じ要求を安全に再処理し、古い更新を拒否します。削除したIDを新規会員へ再利用すると古い削除が新データを消すため、IDを再利用しないか、世代を識別します。
DebeziumのPostgreSQLコネクターでは、行の削除イベントとKafkaログ圧縮向けのnull値のトゥームストーンを区別します。「nullを見たら消す」という独自解釈を先に作らず、配送先の形式と設定に合わせます。配信成功だけでなく保存先での削除結果を記録し、期限を超える未完了を通知します。
バックアップからの復元は、削除記録の再適用まで含む
1月1日のバックアップを1月10日に戻すと、その間に削除したデータは戻り得ます。独自例で120人が退会したなら、その対象と世代を別の記録から再適用します。削除記録は復元するDBだけに置かず、復元で巻き戻らない場所で保護します。保持期間は復元可能な最古の時点、記録の欠損検知、再送可能期間に合わせます。
- 復元先を外部から隔離し、通知・同期・検索公開などの自動処理を止める。
- 復元時点を特定し、以後の削除記録を揃え、完全性を確認する。
- 正本へ削除を再適用し、必要な派生コピーも削除・再構築する。
- 削除済みの識別子が再表示されないことを検証してから公開する。
Google Cloudの資料は、稼働系とバックアップで削除工程が異なることを説明しています。これは自社アプリのすべてのコピーを自動で処理する保証ではありません。削除記録自体にも識別子が残るため、消した本文は保存せず、用途・アクセス権・保持を絞ります。
大量削除は小分けにし、終わった範囲を記録する
大量のDELETEを1トランザクションで流すと、ロックやログ量、レプリカの遅延が問題になります。対象IDを固定し、小さな範囲でコミットして、負荷や遅延に応じて一時停止します。観測に遅れがあるため、停止しきい値は遅延の上限保証にはなりません。Percona pt-archiverはMySQL系の漸進的な処理例で、他DBへそのまま使う手順ではありません。
独自の件数計算として、300万件を1バッチ5,000件で処理するなら600バッチです。1バッチの実時間や待機は別に測り、件数だけから終了時間は約束しません。対象を先へ進めた後で古い対象が混入しないよう、進捗記録の保存と同時更新への対応も決めます。
削除のテストは復元・再送・一部停止を含める
- コピーを1つ停止して退会処理を実行し、未完了が追跡され、復旧後に再処理できることを確認する。
- 古い更新を削除後に再送し、プロフィールが復活しないことを確認する。
- 期限ぴったりの時刻と物理削除前に、直接取得・一覧・キャッシュの利用が拒否されることを確認する。
- 削除前のバックアップを隔離復元し、削除記録の再適用前は公開されず、再適用後は検索を含めて戻らないことを確かめる。
参考にした一次資料
以下の公式資料は2026年10月27日に確認しました。数値例とシナリオは独自の仮定です。設定例は説明用で、実サービスでの実行や性能測定は行っていません。
- DynamoDB — Time to live
- DynamoDB — Expired items
- Debezium — Deletes and tombstones
- Google Cloud — Data deletion
- Percona — pt-archiver