データベースのフェイルオーバーとバックアップ
endpoint、既存接続、複製の反映時点、バックアップの時点を分けて確認し、切替えや復旧後に何を読み直すか説明します。
説明
管理されたDBでも、接続を再利用するアプリ側の処理は必要です。接続先を示すendpointが切り替わっても、すでに開いた接続が自動的に別のDBへ移るとは限りません。また、複製とバックアップでは保持する時点と復旧の目的が異なります。
SQLiteの二つのDBと接続を使い、残高100から200への更新、複製への反映、接続先の切替えを順に進めるモデルを使います。古い接続の失敗を確認して接続し直し、その後、バックアップ後の300と復元時の200を比較します。
endpoint、既存接続、複製の反映時点、バックアップの時点を分けて確認し、切替えや復旧後に何を読み直すか説明します。
1DBと再利用する接続を用意
ServiceはDBのA・Bと現在のendpointを持ち、Poolは接続先を一度選んで接続を保持します。初期データはid=1、balance=100です。
from managed import Service, Pool
service = Service()初期データを持つ二つのDBを用意する
pool = Pool(service)現在のendpointへの接続を保持する
print("endpoint", service.endpoint, "pool", pool.target, "balance", pool.read()保持した接続で残高を取得する)endpointとpoolの接続先はどちらもA、残高は100です。接続を持ち続ける側と、現在の接続先を示す側を別々の値として確認します。
2主側の更新を確定する
Aで残高を200へ更新し、同じ接続から読みます。writeはUPDATE後にcommitします。
pool.write(200)Aの残高を更新して確定する
print("primary", pool.read()更新した接続から確定値を読む)Aでは200が確定しました。別の保存先へその値が反映されたかは、この成功だけでは分かりません。
3複製側の反映前を読む
まだコピーを進めていないBを読みます。
print("replica", service.read_replica()現在のBの内容を読む)Bは100のままです。書込み直後に別の読取り先を使う場合、反映タイミングによって古い値を読む可能性があります。このモデルでは、そのタイミングを次の操作で進めます。
4複製へ確定値を反映する
SQLiteのbackupでAの確定内容をBへコピーします。
service.sync()Aの確定内容をBへ反映する
print("replica", service.read_replica()反映後の値を読む)Bも200になります。切替え先がどの時点までの内容を持つかを確認しました。実際のDB製品では同期方式や遅延の確認方法に従って判断します。
5endpointを切り替えて旧接続を失効させる
保守時の切替えとして、Aの接続を閉じendpointをBへ向けます。Poolが保持していた接続先も表示します。
service.failover()Aを閉じて現在の接続先をBへ替える
print("endpoint", service.endpoint現在の接続先を読む, "pool", pool.targetPoolが保持している接続先を読む)endpointはBですが、poolの記録はAです。接続先の切替えと、既存の接続の差替えは同じ操作ではありません。
6古い接続で読もうとした結果
Poolの古い接続をそのまま使い、発生した例外を表示します。
try:
print(pool.read()古い接続を使って読み込む)
except Exception as error:
print(type(error).__name__, str(error)発生した例外の型と内容を表示する)実際のSQLite接続からProgrammingErrorが返ります。閉じた接続では処理を続けられません。実サービスで返る例外の種類はドライバーなどに依存するため、それに対応した再接続や再試行を設計します。
7現在のendpointへ接続し直す
Poolの接続を現在のendpointから取り直します。
pool.refresh()現在のendpointから接続を取り直す
print("pool", pool.target, "balance", pool.read()新しい接続で残高を取得する)poolはBとなり、同期済みの200を読み出せます。再試行では、古い接続で同じ操作を繰り返すだけでなく、接続自体の更新が必要な場合があります。
8バックアップの後に更新する
200の時点のバックアップを作り、その後で現在のDBを300へ更新します。
service.backup()現在の200を別の保存先へコピーする
pool.write(300)バックアップ後の値へ更新する
print("current", pool.read()現在のDBの値を読む)現在のDBは300です。バックアップは更新前の200を保存した別のDBなので、以後の更新で自動的に300にはなりません。
9復元先と現在のDBを比較
バックアップを新しいSQLite接続へ復元し、保存時点の残高を確認します。
print("restored", service.restored_balance()保存したバックアップを別接続へ復元して読む, "current", pool.read()現在のDBの値と比較する)restoredは200、currentは300です。複製先への切替えとバックアップからの復元では、戻る時点が異なります。復元後に不足する更新をどう扱うかも、復旧設計の一部です。
まとめ
endpointが変わっても、保持した接続は自動的に差し替わりません。
切替え先の反映時点を確認し、古い接続を更新して処理を再開します。
バックアップから戻る時点と、現在のDBにある更新を照合します。
操作履歴と解説を振り返れます。
Python 3 / SQLite
from managed import Service, Pool
service = Service()
pool = Pool(service)
print("endpoint", service.endpoint, "pool", pool.target, "balance", pool.read())endpoint A pool A balance 100
pool.write(200)
print("primary", pool.read())primary 200
print("replica", service.read_replica())replica 100
service.sync()
print("replica", service.read_replica())replica 200
service.failover()
print("endpoint", service.endpoint, "pool", pool.target)endpoint B pool A
try:
print(pool.read())
except Exception as error:
print(type(error).__name__, str(error))ProgrammingError Cannot operate on a closed database.
pool.refresh()
print("pool", pool.target, "balance", pool.read())pool B balance 200
service.backup()
pool.write(300)
print("current", pool.read())current 300
print("restored", service.restored_balance(), "current", pool.read())restored 200 current 300
演習完了