一覧CL-07-1 · 管理されたデータサービス

データベースのフェイルオーバーとバックアップ

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

1
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
2
pool.write(200) print("primary", pool.read())
primary 200
3
print("replica", service.read_replica())
replica 100
4
service.sync() print("replica", service.read_replica())
replica 200
5
service.failover() print("endpoint", service.endpoint, "pool", pool.target)
endpoint B pool A
6
try: print(pool.read()) except Exception as error: print(type(error).__name__, str(error))
ProgrammingError Cannot operate on a closed database.
7
pool.refresh() print("pool", pool.target, "balance", pool.read())
pool B balance 200
8
service.backup() pool.write(300) print("current", pool.read())
current 300
9
print("restored", service.restored_balance(), "current", pool.read())
restored 200 current 300

演習完了