sustain · Stage 4
互換性を保つschema migrationを段階実行する
database changeをexpand、dual write、backfill、dual read、contractへ分け、各段階の観測、停止、rollback、互換性を実行可能な移行計画として設計する。
到達目標
schema changeを互換なexpand-contract phaseと明示的な遷移条件へ分解できる
- expand-contract段階、観測、停止、ロールバックを含む移行計画
- database change managementとPostgreSQL制約を互換性へ結ぶ5分発表
dual write、backfill、dual readの観測から停止とrollbackを判断できる
- expand-contract段階、観測、停止、ロールバックを含む移行計画
- 互換性、観測、停止、rollbackが欠けるmigrationを診断する回答
backfill error rateだけが変わる条件で同じ計画の継続判断を再評価できる
- 互換性、観測、停止、rollbackが欠けるmigrationを診断する回答
- backfill error rateだけを変えて継続・停止・rollbackを再評価した記録
能力の進行
recognize
expand、dual write、backfill、dual read、contractの責務を区別できる
証拠: expand-contract段階、観測、停止、ロールバックを含む移行計画
explain
old readerとnew readerを同時に支える期間が必要な理由を説明できる
証拠: database change managementとPostgreSQL制約を互換性へ結ぶ5分発表
apply
各phaseへ観測、停止条件、rollback、compatibilityを割り当てられる
証拠: expand-contract段階、観測、停止、ロールバックを含む移行計画
diagnose
backfill成功件数だけに隠れたerror、drift、reader incompatibilityを反証できる
証拠: 互換性、観測、停止、rollbackが欠けるmigrationを診断する回答
lead
application、database、operationsのownerと停止権限を含む段階移行を主導できる
証拠: backfill error rateだけを変えて継続・停止・rollbackを再評価した記録
なぜ重要か
database schemaはapplication version、batch、replica、運用手順が共有する契約である。破壊的変更を一度に行うと、DDL command自体が成功してもold reader、遅延したwriter、backfill、logical replicationのいずれかが壊れる。安全なmigrationは「実行できたか」ではなく、混在versionでsystem outcomeを保てるかを段階ごとに検証する。
DORAのdatabase change managementは小さく頻繁でapplicationと協調した変更を重視する。PostgreSQL 18のALTER TABLEは操作ごとのlockと制約を確認する一次資料であり、logical replicationのRestrictionsはschema定義が自動複製されない等の境界を示す。一般論をそのまま適用せず、対象version、table規模、replication構成で検証する。
メンタルモデル
migrationを五つのstateへ分ける。expandで互換な構造を追加し、dual writeで新旧表現を同時更新し、backfillで既存rowを移す。dual readで新旧読取結果を比較し、十分な観測後にだけcontractで旧構造を除く。次phaseへの遷移は時刻ではなく検証済みのguardから導く。
rollbackは必ずしもDDLの逆操作ではない。feature flagでold readへ戻す、dual writeを止める、backfill checkpointへ戻る、contractを延期するなど、各phaseで安全な回復形が異なる。不可逆なcontractには、旧利用者がいないことを示す観測gateが必要である。
| phase | old reader | new reader | old writer | new writer | backfill / recovery |
|---|---|---|---|---|---|
| expand | allowed — old reader remains valid | blocked — new reader is not deployed | allowed — old writer remains valid | blocked — dual write is not enabled | stop — backfill has not started |
| dual-write | allowed — old reader remains valid | fallback — new reader may use old field | allowed — old writer remains valid | required — dual write begins | stop — parity failure disables new-field write |
| backfill | allowed — old reader remains valid | fallback — new reader retains old field | allowed — old writer remains valid | required — dual write continues | rollback — pause at checkpoint on error or lag |
| dual-read | allowed — old reader remains valid | required — compare new result with fallback | allowed — old writer remains valid | required — dual write continues | fallback — route reads to old field on mismatch |
| contract | blocked — old reader usage must be zero | required — new reader is verified | blocked — old writer usage must be zero | required — new writer is verified | rollback — defer contract if any old usage returns |
注記
図を読む際の補足情報です。
- この注記は旧図の読み順を保持する補助です。
- expand: old readerを壊さないnullable構造を追加する。
- dual write: 新旧fieldへ書き、成功率と値のparityを観測する。
- backfill: bounded batchで既存rowを移しerror rateとlagを測る。
- dual read: 新旧readerの結果差を比較しfallbackを保持する。
- contract: 利用停止の証拠を確認してから旧構造を除く。
- rollback: 各stateで戻す対象と回復確認を先に定義する。
dual write、backfill、dual readの各phaseで互換性gateが失敗した時、どの安全状態へ戻すか。
- 初期状態: expand: old readerを壊さないnullable構造を追加する。
- dual write: 新旧fieldへ書き、成功率と値のparityを観測する。
- backfill: bounded batchで既存rowを移しerror rateとlagを測る。
- dual read: 新旧readerの結果差を比較しfallbackを保持する。
- contract: 利用停止の証拠を確認してから旧構造を除く。
- dual write停止・旧構造互換: 新fieldへのwriteを停止し、old readerが旧fieldから正しい値を読める状態へ戻す。
- backfill停止・旧構造互換: bounded batchを停止し、dual writeと旧fieldを維持して未移行rowを安全に残す。
- old readへfallback: 新readerを停止し、old readerの結果へfallbackして利用者結果を回復する。
- 互換性回復を検証済み: old reader成功率、値のparity、error rate、利用者結果を再観測して安全状態を確認する。
| イベント | 開始 | 終了 | 判定 | 理由 |
|---|---|---|---|---|
| next | expand | dual write | allowed | — |
| next | dual write | backfill | allowed | — |
| next | backfill | dual read | allowed | — |
| next | dual read | contract | allowed | — |
| reset | dual write | dual write停止・旧構造互換 | allowed | — |
| reset | backfill | backfill停止・旧構造互換 | allowed | — |
| reset | dual read | old readへfallback | allowed | — |
| next | dual write停止・旧構造互換 | 互換性回復を検証済み | allowed | — |
| next | backfill停止・旧構造互換 | 互換性回復を検証済み | allowed | — |
| next | old readへfallback | 互換性回復を検証済み | allowed | — |
| timer | dual write | backfill | rejected | 成功率または値のparityがgateを満たさない間はbackfillへ進まない。 |
| timer | backfill | dual read | rejected | backfillのerror rateまたはlagがgateを満たさない間はdual readへ進まない。 |
| timer | dual read | contract | rejected | 旧構造の利用停止証拠が揃うまでcontractを開始しない。 |
phase別の停止・rollback先、旧reader互換性、復旧観測、contractへの拒否条件を説明できる。
- expand ready: expandでold/new readerが共存できるnullable構造を用意し、compatibilityを確認する。; 条件 常時; node
expand; edge なし - dual write compatible: 新旧fieldへのwrite parityを観測し、old reader compatibilityを維持する。; 条件 常時; node
dual-write; edgeexpand-to-dual-write - migrate running: migrate phaseでbounded backfillを実行し、error rateとlagを観測する。; 条件 常時; node
backfill; edgedual-write-to-backfill - backfill paused: threshold超過で停止し、compatibility証拠がないunsafe contractをblockedにする。; 条件 常時; node
backfill、backfill-compatible; edgebackfill-stop - rollback complete: rollback後も旧fieldとdual writeを維持し、old readerの結果を回復確認する。; 条件 常時; node
backfill-compatible、restoration-verified; edgebackfill-verify - migrate retried: 原因解消後にbounded migrateを再開し、error rateとlagを再観測する。; 条件 常時; node
backfill; edgedual-write-to-backfill - compatibility verified: 新旧readerの同値性と旧構造の利用停止を確認してcontract gateを開く。; 条件 常時; node
dual-read; edgebackfill-to-dual-read - contract complete: compatibility証拠が揃った後だけ旧構造をcontractし、移行結果を再観測する。; 条件 常時; node
contract; edgedual-read-to-contract
| イベント | 開始 | 終了 | 条件 |
|---|---|---|---|
| next | expand-ready | dual-write-compatible | 常時 |
| timer | expand-ready | dual-write-compatible | 常時 |
| next | dual-write-compatible | migration-running | 常時 |
| timer | dual-write-compatible | migration-running | 常時 |
| next | migration-running | backfill-paused | 常時 |
| timer | migration-running | backfill-paused | 常時 |
| next | backfill-paused | rollback-complete | 常時 |
| timer | backfill-paused | rollback-complete | 常時 |
| next | rollback-complete | migration-retried | 常時 |
| timer | rollback-complete | migration-retried | 常時 |
| next | migration-retried | compatibility-verified | 常時 |
| timer | migration-retried | compatibility-verified | 常時 |
| next | compatibility-verified | contract-complete | 常時 |
| timer | compatibility-verified | contract-complete | 常時 |
| previous | dual-write-compatible | expand-ready | 常時 |
| previous | migration-running | dual-write-compatible | 常時 |
| previous | backfill-paused | migration-running | 常時 |
| previous | rollback-complete | backfill-paused | 常時 |
| previous | migration-retried | rollback-complete | 常時 |
| previous | compatibility-verified | migration-retried | 常時 |
| previous | contract-complete | compatibility-verified | 常時 |
| reset | dual-write-compatible | expand-ready | 常時 |
| reset | migration-running | expand-ready | 常時 |
| reset | backfill-paused | expand-ready | 常時 |
| reset | rollback-complete | expand-ready | 常時 |
| reset | migration-retried | expand-ready | 常時 |
| reset | compatibility-verified | expand-ready | 常時 |
| reset | contract-complete | expand-ready | 常時 |
| 結果 | 状態 |
|---|---|
| unsafe contractをblockedにして停止する | backfill-paused |
| rollback後のcompatibilityを再観測する | rollback-complete |
| 互換性証拠の後だけcontractを完了する | contract-complete |
現在の状態: expand ready — expandでold/new readerが共存できるnullable構造を用意し、compatibilityを確認する。
このモデルは例示的かつ決定的であり、実システムの完全な再現ではありません。
動く例で考える
顧客名fieldを五phaseで移行する
- 前提
- lesson-defined simulated migrationであり、実database、実customer row、production lock時間を観測したものではない。PostgreSQL固有の実行計画を保証せず、phase判断の因果だけを検査する。
- 入力
- expand、dual write、backfill、dual read、contractの固定plan、old/new reader成功、dual write parity、backfill error rateとthresholdを与える。
- 操作
- 各phaseのcompatibility、observation、stop condition、rollbackを入力から構成する。baselineは低いbackfill error rate、transferはその値だけを高くし、同じplanへ判断関数を適用する。
- 観測
- baselineはcontinue、transferはrollbackになる。判断を常にcontinueへ差し替えるとmigration-causal-invariantが検知する。
- 結論
- command successはsystem outcomeではない。互換性とerror thresholdを満たす観測がcontinueを支え、違反時に実行されたrollback結果が安全性の証拠になる。
python3.13 - <<'PY'
import json
HARNESS = "migration_state_machine_lab_v1"
TRANSFER_TASK = (
"backfill error rateだけを変え、同じ移行計画の継続・停止・"
"ロールバック判断を再評価する"
)
CANONICAL_PHASE_NAMES = (
"expand",
"dual-write",
"backfill",
"dual-read",
"contract",
)
PHASE_INPUTS = [
{
"name": "expand",
"compatibility": "nullable customer_name_v2 keeps old reader valid",
"observation": "schema exists and old reader fixture succeeds",
"stop_condition": "old reader failure or blocking lock exceeds budget",
"rollback": "stop rollout and remove unused nullable field",
},
{
"name": "dual-write",
"compatibility": "old and new names are written from one input",
"observation": "write parity and write error rate",
"stop_condition": "parity mismatch or write error exceeds threshold",
"rollback": "disable new-field write and retain old-field write",
},
{
"name": "backfill",
"compatibility": "old field remains source for old reader",
"observation": "processed rows, error rate, checkpoint and replica lag",
"stop_condition": "backfill error rate exceeds threshold",
"rollback": "pause at checkpoint and clear unverified new values",
},
{
"name": "dual-read",
"compatibility": "new reader falls back to old field",
"observation": "old and new read result parity",
"stop_condition": "read mismatch or fallback rate exceeds threshold",
"rollback": "route all reads to old field",
},
{
"name": "contract",
"compatibility": "old reader usage is zero before removal",
"observation": "old-field read and write counters remain zero",
"stop_condition": "any old reader or writer is observed",
"rollback": "defer removal and restore compatibility window",
},
]
THRESHOLDS = {
"max_backfill_error_rate": 0.01,
}
BASELINE_OBSERVATION = {
"old_reader_successes": 200,
"old_reader_attempts": 200,
"new_reader_successes": 200,
"new_reader_attempts": 200,
"dual_write_matches": 200,
"dual_write_attempts": 200,
"backfill_rows": 500,
"backfill_errors": 2,
}
TRANSFER_OBSERVATION = {
"old_reader_successes": 200,
"old_reader_attempts": 200,
"new_reader_successes": 200,
"new_reader_attempts": 200,
"dual_write_matches": 200,
"dual_write_attempts": 200,
"backfill_rows": 500,
"backfill_errors": 20,
}
ROLLBACK_OBSERVATION = {
"executed": True,
"observed": True,
"expected_state": "backfill-paused-at-checkpoint",
"actual_state": "backfill-paused-at-checkpoint",
}
OBSERVATION_FIELDS = {
"old_reader_successes",
"old_reader_attempts",
"new_reader_successes",
"new_reader_attempts",
"dual_write_matches",
"dual_write_attempts",
"backfill_rows",
"backfill_errors",
}
PHASE_FIELDS = {
"name",
"compatibility",
"observation",
"stop_condition",
"rollback",
}
ROLLBACK_FIELDS = {
"executed",
"observed",
"expected_state",
"actual_state",
}
def validate_migration_inputs(baseline, transferred, thresholds):
for observation in (baseline, transferred):
if (
type(observation) is not dict
or set(observation) != OBSERVATION_FIELDS
or not all(
type(value) is int and value >= 0
for value in observation.values()
)
or observation["old_reader_attempts"] <= 0
or observation["new_reader_attempts"] <= 0
or observation["dual_write_attempts"] <= 0
or observation["backfill_rows"] <= 0
):
raise AssertionError(
"migration-transfer-invariant: invalid observation schema"
)
if (
type(thresholds) is not dict
or set(thresholds) != {"max_backfill_error_rate"}
or type(thresholds["max_backfill_error_rate"])
not in {int, float}
or type(thresholds["max_backfill_error_rate"]) is bool
or thresholds["max_backfill_error_rate"] < 0
):
raise AssertionError(
"migration-transfer-invariant: invalid threshold schema"
)
changed = [
field
for field in sorted(set(baseline) | set(transferred))
if (
field not in baseline
or field not in transferred
or baseline[field] != transferred[field]
)
]
if changed != ["backfill_errors"]:
raise AssertionError(
"migration-transfer-invariant: transfer changed wrong fields"
)
return changed
def ratio(numerator, denominator):
if denominator <= 0:
raise AssertionError(
"migration-causal-invariant: denominator must be positive"
)
return numerator / denominator
def derive_observation(source):
return {
"old_reader_supported": (
source["old_reader_successes"] == source["old_reader_attempts"]
),
"new_reader_supported": (
source["new_reader_successes"] == source["new_reader_attempts"]
),
"dual_write_verified": (
source["dual_write_matches"] == source["dual_write_attempts"]
),
"backfill_error_rate": ratio(
source["backfill_errors"],
source["backfill_rows"],
),
}
def decide_migration(observation, thresholds):
compatible = (
observation["old_reader_supported"]
and observation["new_reader_supported"]
and observation["dual_write_verified"]
)
within_error_budget = (
observation["backfill_error_rate"]
<= thresholds["max_backfill_error_rate"]
)
return "continue" if compatible and within_error_budget else "rollback"
def evaluate(source, thresholds):
observation = derive_observation(source)
decision = decide_migration(observation, thresholds)
# decisionの置換を見逃さないため、入力から独立に導いたguardと
# state machineの出力を照合してfail closedにする。
guard_allows_continue = (
observation["old_reader_supported"]
and observation["new_reader_supported"]
and observation["dual_write_verified"]
and observation["backfill_error_rate"]
<= thresholds["max_backfill_error_rate"]
)
expected_decision = (
"continue" if guard_allows_continue else "rollback"
)
if decision != expected_decision:
raise AssertionError(
"migration-causal-invariant: decision bypassed observed guard"
)
return {
"observation": observation,
"decision": decision,
}
def build_phases(phase_inputs):
# phaseごとの停止と回復を同じ構造へ必須化し、happy pathだけの
# 手順がcontractまで流れることを防ぐ。
if (
type(phase_inputs) is not list
or not phase_inputs
or any(
type(phase) is not dict
or set(phase) != PHASE_FIELDS
or not all(
type(value) is str and value
for value in phase.values()
)
for phase in phase_inputs
)
):
raise AssertionError(
"migration-plan-invariant: invalid phase schema"
)
if (
tuple(phase["name"] for phase in phase_inputs)
!= CANONICAL_PHASE_NAMES
):
raise AssertionError(
"migration-plan-invariant: noncanonical phase sequence"
)
phases = [
{
"name": phase["name"],
"compatibility": phase["compatibility"],
"observation": phase["observation"],
"stop_condition": phase["stop_condition"],
"rollback": phase["rollback"],
}
for phase in phase_inputs
]
if not all(
phase["compatibility"]
and phase["observation"]
and phase["stop_condition"]
and phase["rollback"]
for phase in phases
):
raise AssertionError(
"migration-causal-invariant: incomplete phase"
)
return phases
def verify_rollback(decision, observation):
if (
type(observation) is not dict
or set(observation) != ROLLBACK_FIELDS
or type(observation["executed"]) is not bool
or type(observation["observed"]) is not bool
or type(observation["expected_state"]) is not str
or not observation["expected_state"]
or type(observation["actual_state"]) is not str
or not observation["actual_state"]
):
raise AssertionError(
"migration-rollback-outcome-invariant: invalid schema"
)
system_outcome = (
decision == "rollback"
and observation["executed"]
and observation["observed"]
and observation["actual_state"]
== observation["expected_state"]
)
if decision == "rollback" and not system_outcome:
raise AssertionError(
"migration-rollback-outcome-invariant: "
"rollback outcome was not observed"
)
return {
"executed": observation["executed"],
"observed": observation["observed"],
"expected_state": observation["expected_state"],
"actual_state": observation["actual_state"],
"system_outcome": system_outcome,
}
def main():
phases = build_phases(PHASE_INPUTS)
transferred_plan = build_phases(PHASE_INPUTS)
if phases != transferred_plan:
raise AssertionError(
"migration-plan-invariant: plan snapshots drifted"
)
changed_input_fields = validate_migration_inputs(
BASELINE_OBSERVATION,
TRANSFER_OBSERVATION,
THRESHOLDS,
)
baseline = evaluate(BASELINE_OBSERVATION, THRESHOLDS)
transferred = evaluate(TRANSFER_OBSERVATION, THRESHOLDS)
if (
baseline["decision"] != "continue"
or transferred["decision"] != "rollback"
):
raise AssertionError(
"migration-causal-invariant: transfer changed wrong cause"
)
rollback_outcome = verify_rollback(
transferred["decision"],
ROLLBACK_OBSERVATION,
)
compatibility = {
"old_reader_supported": (
baseline["observation"]["old_reader_supported"]
),
"new_reader_supported": (
baseline["observation"]["new_reader_supported"]
),
"dual_write_verified": (
baseline["observation"]["dual_write_verified"]
),
}
report = {
"harness": HARNESS,
"fixture_metadata": {
"kind": "simulated",
"provenance": "lesson-defined customer-name migration inputs",
"limitations": (
"simulated countsでありproduction lock、traffic、"
"replication lag、PostgreSQL実行結果を表さない"
),
"synthetic_or_observed_explicit": (
"値はすべてsimulated入力またはその計算結果"
),
},
"runtime_bound": {
"records": (
len(PHASE_INPUTS)
+ len(BASELINE_OBSERVATION)
+ len(TRANSFER_OBSERVATION)
),
"subprocesses": 0,
"maximum_iterations": (
len(PHASE_INPUTS) + len(BASELINE_OBSERVATION)
),
},
"external_network_used": False,
"migration_plan": {
"phases": phases,
},
"compatibility": compatibility,
"backfill_error_rate_transfer": {
"changed_assumption": "backfill-error-rate",
"changed_fields": ["backfill_error_rate"],
"same_migration_plan": phases == transferred_plan,
"baseline_plan": phases,
"transferred_plan": transferred_plan,
"baseline_error_rate": (
baseline["observation"]["backfill_error_rate"]
),
"transferred_error_rate": (
transferred["observation"]["backfill_error_rate"]
),
"baseline_decision": baseline["decision"],
"transferred_decision": transferred["decision"],
"rollback_outcome": rollback_outcome,
},
"command_success_distinction": {
"command_completed": True,
"system_outcome_checked": (
all(compatibility.values())
and baseline["decision"] == "continue"
and transferred["decision"] == "rollback"
and rollback_outcome["system_outcome"]
),
"command_success_equals_system_outcome": False,
"outcome_evidence": (
"reader compatibility、write parity、backfill error、"
"観測済みrollback system outcomeを検査"
),
},
"mastery_evidence": {
"lab_steps": [
{"step": 1, "evidence": phases},
{"step": 2, "evidence": baseline["observation"]},
{"step": 3, "evidence": rollback_outcome},
],
"assessments": [
{"assessment": 1, "evidence": compatibility},
{
"assessment": 2,
"evidence": transferred["observation"],
},
],
"rubric_dimensions": [
"technical-correctness",
"judgment",
"evidence",
"communication",
],
"transfer": {
"task": TRANSFER_TASK,
"changed_assumption": "backfill-error-rate",
"evidence": transferred["decision"],
},
},
}
print(json.dumps(report, ensure_ascii=False, sort_keys=True))
try:
main()
except AssertionError as error:
raise SystemExit(str(error)) from None
PY
トレードオフと失敗モード
| 観測 | 判断 | rollback outcome |
|---|---|---|
| reader互換、write parity、error budget内 | 観測を継続して次phase候補とする | 不要。current stateを維持する |
| backfill error rateがthreshold超過 | backfillをstopしてrollbackする | checkpoint停止と未検証値の隔離を確認する |
| contract前にold readerを観測 | 削除を延期する | compatibility windowとold readを維持する |
- 誤診: ALTER TABLE commandが成功したのでmigrationは完了した。反証: command successはreader compatibility、dual write parity、backfill error、replica、利用者可視のsystem outcomeを検証していない。
- 誤診: dual write期間を短くすれば不整合riskも必ず減る。反証: 観測sampleが不足すれば低頻度のmismatchを見逃し、contract後にrollback不能な破壊へ進む。
- 誤診: rollbackは逆向きDDLを用意することである。反証: phaseごとにold readへ戻す、writeを止める、checkpointで停止するなど回復形が異なり、回復後のsystem outcome確認まで必要である。
知識チェック
- expand、dual write、backfill、dual read、contractの順序が互換性を作る理由を説明せよ。
- backfill processed rowsだけではcontinueを判断できない理由は何か。
- contract前にold readerを一件観測した時のstopとrollbackを定義せよ。
- backfill error rateだけを変えるtransferで固定すべきplan証拠は何か。
出典と次の学習
DORA Capability: Database change management(accessed 2026-07-31)をdelivery practiceの根拠に、PostgreSQL 18 Documentation: ALTER TABLEをDDLとlockの確認に、PostgreSQL 18 Documentation: Restrictionsをlogical replication境界の確認に使う。完全な書誌情報と参照先はlesson metadataに分離している。
次はcore-23で、migrationを含むincidentのevidence timeline、意思決定、contributing factor、検証可能なactionをnon-blaming reviewへ変換する。
実践ラボ
顧客名columnのexpand-contract migrationを設計する
提出成果物: expand-contract段階、観測、停止、ロールバックを含む移行計画
- old readerとnew readerを同時に支える五つのphase、観測、停止条件、rollbackを定義する
- fixed simulated observationからcompatibilityとbackfill error rateを検査しcontinue判断を導く
- backfill error rateだけを上げ、同じmigration planがrollbackへ遷移することを検証する
説明して理解を確かめる
5分で、DORA database change management、PostgreSQL 18 ALTER TABLE、logical replication Restrictionsを、具体的なexpand-contract順序と停止判断へどう変換するか説明する。
アセスメント
問い: new columnを追加して全rowを一括更新し、直後にold columnを削除する計画を評価せよ。
期待する証拠: lock、old reader、new reader、dual write、backfill、観測、contract gate、rollback
問い: migration commandが成功したので配信完了とする判断を評価せよ。
期待する証拠: command successとsystem outcomeの区別、reader compatibility、write parity、backfill error、rollback outcome
別問題へ転用する
backfill error rateだけを変え、同じ移行計画の継続・停止・ロールバック判断を再評価する
復習スケジュール
- 1日後
expandとcontractの間にold readerを支える理由は何か
- 7日後
backfillで件数以外に観測すべき結果は何か
- 30日後
command successとmigration system outcomeをどう分けるか
- 90日後
expandとcontractの間にold readerを支える理由は何か
評価ルーブリック
| 観点 | 未達 | 発展途上 | 熟達 | 卓越 |
|---|---|---|---|---|
| technical-correctness | 破壊的DDLを一段で実行し互換期間がない | phaseはあるがreader、writer、backfillの互換性が曖昧である | 五phaseと観測、停止、rollback、compatibilityを一貫して追跡する | retry、replication lag、混在versionでも遷移不変条件を検査できる |
| judgment | DDL command successだけでcontractへ進む | metricは見るが停止thresholdと権限がない | error、drift、互換性、可逆性からcontinue、stop、rollbackを選ぶ | 業務riskと回復時間に応じてphase幅と観測期間を調整する |
| evidence | 手順書だけで実行観測がない | 件数はあるがerror rate、reader結果、限界がない | 固定入力、threshold、phase observation、decisionを再計算可能に残す | rollback後のsystem outcomeとcontract前の証拠を監査可能にする |
| communication | DB担当だけが順序を知っている | phase名はあるがownerと停止条件が読めない | 各phaseの互換性、観測、停止、rollbackを職能横断で共有できる | application、database、operationsが同じstate machineで異議と再開を判断できる |
出典
以下の外部資料は利用者が選択したときだけ開きます。