RTOとRPOによる復旧の評価
復旧の速さと復元データの時点を混同せず、目標を満たすかを時刻と更新IDで説明します。
説明
RTOは復旧までに許容する時間の目標、RPOはどの時点までのデータ喪失を許容するかの目標です。復旧操作を開始した時刻だけでなく、利用者の要求が再び成功した時刻と、復元先に残る確定更新を確認します。
10:00の障害に対し、RTO180秒・RPO60秒の目標を持つ固定記録を使います。原DBには3件の確定更新、別zoneの待機DBには2件、別regionのバックアップには1件あります。SQLiteのコピーと復元を使い、待機系への切替えとバックアップ復元を比較します。
復旧の速さと復元データの時点を混同せず、目標を満たすかを時刻と更新IDで説明します。
1配置と復旧目標
primaryはzone-a、standbyはzone-b、backupは別regionという固定配置です。各DBのコピーはそれぞれの時点で作成済みです。
from recovery import Recovery
recovery = Recovery()異なる時点の三つのSQLiteを用意する
print(recovery.layout主側・待機側・保存先の配置を読む)
print("failure", recovery.failure, "targets", recovery.targetsRTOとRPOの目標を読む)障害時刻は10:00、目標は復旧180秒以内・データ時点60秒以内です。配置の分離と保存するデータの時点を、両方確認します。
2障害前に確定した更新を読む
原DBに確定済みだった更新ID、時刻、値を読みます。
print(recovery.rows("primary")障害前の確定更新を読む)ID1は09:58:00、ID2は09:59:10、ID3は09:59:50です。復元先の内容を、この確定済みの一覧と照合します。
3待機DBとバックアップの保存時点
二つの復旧元を読みます。
print("standby", recovery.rows("standby")待機DBに反映された更新を読む)
print("backup", recovery.rows("backup")バックアップ時点の更新を読む)待機DBはID1・2、バックアップはID1だけです。複数の保存先が存在することと、障害直前の更新まで保持していることは別です。
4切替え操作の完了を復旧完了としない
記録では待機DBの昇格が10:02、最初の利用者要求の成功が10:03です。後者を復旧完了時刻として評価します。
recovery.compare("standby", "2026-11-01T10:03:00")最初の要求成功時刻と待機側の更新時点を評価する停止時間は180秒でRTO目標内です。待機側の最後の確定時刻は09:59:10なので障害時点との差は50秒、RPO目標60秒以内です。ただしID3は失われています。目標達成がデータ喪失ゼロを意味するわけではありません。
5バックアップから別DBへ復元する
バックアップを新しいSQLite接続へ復元し、保存されていた更新を読みます。
recovery.restore()バックアップを別のDBへ復元する
print(recovery.rows("restored")復元先の確定更新を読む)復元先にはID1だけがあります。復元処理が成功しても、バックアップ以後のID2・3が自動的に追加されることはありません。
6復旧が遅くデータも古い場合
この復元案では最初の要求成功が10:08という固定記録で評価します。
recovery.compare("restored", "2026-11-01T10:08:00")バックアップ復元案を同じ目標で評価する停止時間480秒、データ時点は障害の120秒前で、両方の目標を超えます。RTOとRPOのどちらが満たせないのかを別々に確認すると、準備時間と保存間隔のどちらを改善すべきかを判断できます。
7別に残った確定ログを適用する
このシナリオでは、確定済み3件の業務ログが別に保持されています。IDを重複させずに復元先へ適用します。
recovery.replay()残った確定記録を復元先へ適用する
print(recovery.rows("restored")不足していた更新がそろったか確認する)ID1〜3がそろいます。バックアップの不足を埋められるかは、ログが残っている範囲と、再適用して正しい結果になる処理に依存します。ここでは主キーIDとINSERT OR IGNOREで同じ記録の重複を避けています。
8再適用とデータ復旧の範囲を確認
同じ固定ログをもう一度適用し、原DBの確定一覧と比較します。
recovery.replay()同じ記録を再適用する
print("same committed rows", recovery.rows("restored") == recovery.rows("primary")確定更新の集合を比較する)
print("count", len(recovery.rows("restored"))重複が増えていないことを確認する)一致はTrue、件数は3のままです。この資料にある確定更新はすべて復元できました。追加の適用に必要な時間も含めて、利用者要求が成功する時刻を再評価する必要があります。
まとめ
RTOとRPOは目標であり、復旧記録の時間とデータ時点を照合して評価します。
待機系があることや復元処理の成功だけでは、目標達成を判断できません。
バックアップ以後の更新を復元するには、別に保持した確定記録と再適用の方法が必要です。
操作履歴と解説を振り返れます。
Python 3
from recovery import Recovery
recovery = Recovery()
print(recovery.layout)
print("failure", recovery.failure, "targets", recovery.targets){'primary': 'zone-a', 'standby': 'zone-b', 'backup': 'region-b'}
failure 2026-11-01T10:00:00 targets {'RTO_seconds': 180, 'RPO_seconds': 60}print(recovery.rows("primary"))[(1, '2026-11-01T09:58:00', 100), (2, '2026-11-01T09:59:10', 200), (3, '2026-11-01T09:59:50', 300)]
print("standby", recovery.rows("standby"))
print("backup", recovery.rows("backup"))standby [(1, '2026-11-01T09:58:00', 100), (2, '2026-11-01T09:59:10', 200)] backup [(1, '2026-11-01T09:58:00', 100)]
recovery.compare("standby", "2026-11-01T10:03:00")recovery_seconds=180 data_age_seconds=50 missing_ids=[3] RTO_target_180_met=True RPO_target_60_met=True
recovery.restore()
print(recovery.rows("restored"))[(1, '2026-11-01T09:58:00', 100)]
recovery.compare("restored", "2026-11-01T10:08:00")recovery_seconds=480 data_age_seconds=120 missing_ids=[2, 3] RTO_target_180_met=False RPO_target_60_met=False
recovery.replay()
print(recovery.rows("restored"))[(1, '2026-11-01T09:58:00', 100), (2, '2026-11-01T09:59:10', 200), (3, '2026-11-01T09:59:50', 300)]
recovery.replay()
print("same committed rows", recovery.rows("restored") == recovery.rows("primary"))
print("count", len(recovery.rows("restored")))same committed rows True count 3
演習完了