data-scale · Stage 3
アクセスパターンと制約からストレージADRを再計算する
query shape、不変条件、更新競合、成長予測を証拠に、relational・document・key-valueモデルを同じ基準で比較する。
到達目標
query shape、同時に読むdata、頻度、不変条件、更新競合、成長予測を、ストレージ製品とは独立したworkload契約として記述できる
- query shape、不変条件、更新競合、成長予測、同一criteriaでの三モデル比較、負の帰結、confirmationを含む機械可読ストレージADR
- data modelを製品名やschemaの有無だけで選べない理由を説明する6分間の解説
relational・document・key-valueモデルをaccess fit、constraint、capacity、operations、recoveryの同じ加重基準で比較し、観測証拠からscoreを再計算できる
- query shape、不変条件、更新競合、成長予測、同一criteriaでの三モデル比較、負の帰結、confirmationを含む機械可読ストレージADR
- constraint・capacity・operations・recoveryの証拠を用いてratingを反証し、access pattern変更後のscoreを再計算する判断記録
アクセスパターンの前提変更で選択肢のratingとwinnerを再計算し、負の帰結と確認方法を含むADRを更新できる
- constraint・capacity・operations・recoveryの証拠を用いてratingを反証し、access pattern変更後のscoreを再計算する判断記録
- 注文検索を顧客中心から商品・期間中心へ変え、変更した前提、再計算結果、移行と回復条件を記録したADR
能力の進行
recognize
entity、value、relationship、query shape、不変条件、更新競合、成長予測を識別できる
証拠: query shape、不変条件、更新競合、成長予測、同一criteriaでの三モデル比較、負の帰結、confirmationを含む機械可読ストレージADR
explain
論理data modelと物理配置を分け、relational・document・key-valueの適合条件と限界を説明できる
証拠: data modelを製品名やschemaの有無だけで選べない理由を説明する6分間の解説
apply
同じweightとcriteriaで三モデルを評価し、constraint、capacity、operations、recoveryの証拠をADRへ接続できる
証拠: query shape、不変条件、更新競合、成長予測、同一criteriaでの三モデル比較、負の帰結、confirmationを含む機械可読ストレージADR
diagnose
schema-lessや単一table、partitionを万能解とする誤診を、workloadと運用証拠から反証できる
証拠: constraint・capacity・operations・recoveryの証拠を用いてratingを反証し、access pattern変更後のscoreを再計算する判断記録
lead
access pattern変更時にrating、winner、移行境界、confirmationを再評価するreviewを主導できる
証拠: 注文検索を顧客中心から商品・期間中心へ変え、変更した前提、再計算結果、移行と回復条件を記録したADR
なぜ重要か
data modelの選択は製品カテゴリ当てではない。利用者がどのdataを一緒に読み、どの不変条件を一度に守り、どこで更新が競合し、件数がどう増えるかを明示して初めて、storageの適合性を比較できる。初期のqueryには合う設計も、主要なaccess patternが変われば最適ではなくなる。
ADRの役割は永久に正しい製品を宣言することではなく、判断時のworkload、同じ比較基準、負の帰結、確認方法を残すことである。logical model、indexやpartition keyなどのphysical design、製品固有機能を分けると、前提変更時にどこから再評価すべきかを追跡できる。
メンタルモデル
最初にdomainのentity、value、relationship、不変条件を記述し、次にquery shapeへ「頻度」と「同時に読むdata」を付ける。さらに更新競合と成長予測を加えれば、access pathの局所最適化が整合性や復旧を壊さないかを比較できる。
| model | access fit | constraint | operations・recovery | 主な負の帰結 |
|---|---|---|---|---|
| relational | 顧客履歴とad-hoc条件をindex・joinで扱う | 一意性・参照・transaction境界を宣言する | migration、backup、point-in-time recoveryを演習する | join、index維持、scale-up上限を測る必要 |
| document | 注文aggregateを一緒に読むqueryへ適合 | validationとapplication invariantを併用 | document肥大、index、restoreを監視する | 重複と部分更新競合、横断queryの追加index |
| key-value | 既知keyの固定queryを低いfan-outで読む | 複数key不変条件の責任を明示する | hot key、再構築、backup復旧を演習する | 二次access pathと再同期の複雑性 |
vendor scope: relationalの例はPostgreSQL 18のdata definition、documentの例はMongoDB 8.0のdata modeling、key-valueの機構は2007年Dynamo論文modelに限定する。製品version間の機能差やmanaged serviceのSLAを一般化しない。
fixtureの由来: 注文件数、query frequency、rating evidenceはlesson用に作ったsynthetic scenarioであり、production traceや実vendor benchmarkではない。実判断では匿名化したquery log、cardinality、latency分位、更新競合、backup・restore演習で置き換える。
注記
図を読む際の補足情報です。
- domain: 注文番号の一意性、合計額と明細合計の一致、注文状態遷移を不変条件にする。
- workload: 顧客履歴、注文detail、商品・期間検索、状態更新を一意なquery IDと時間当たり頻度で表す。
- growth: 現在件数と月間増加から12か月後を計算し、synthetic projectionである限界を残す。
- comparison: relational、document、key-valueをaccess fit、constraint、capacity、operations、recoveryの同じweightで採点する。
- ADR: winnerだけでなく、負の帰結、移行境界、confirmationを記録する。
- mutation: 商品・期間検索が主要queryになったらfrequencyだけを変える。query-fit ratingは固定し、access-fit rating、weighted score、winnerを再計算する。
- worked exampleでは全optionへ同じweight set 0.45/0.20/0.15/0.10/0.10を適用する。query-fit ratingは固定し、frequency変更からaccess-fit ratingを導出する。
- baseline total 4.288957; winner relational; changed total 3.668868; winner key-value
query frequencyだけを変え、固定したquery-fit ratingから何を再計算するか。
| 項目 | access fit × 0.45query frequency×model別ratingの加重平均。baselineと変更後を同じ式で再計算する。 | constraint × 0.20不変条件と更新競合のratingとscore contribution。 | capacity × 0.151.1M projected recordsへのtested pathのratingとcontribution。 | operations × 0.10migration・observe・operateのratingとcontribution。 | recovery × 0.10backup・restore・reconstructionのratingとcontribution。 |
|---|---|---|---|---|---|
| relational関係・constraint・transactionを明示する候補。 | baseline (480×5 + 360×3 + 48×1 + 90×4) / 978 = 3.975460; changed (24×5 + 360×3 + 480×1 + 90×4) / 954 = 2.138365; weight 0.45 | rating 5 × 0.20 = 1.00 | rating 4 × 0.15 = 0.60 | rating 4 × 0.10 = 0.40 | rating 5 × 0.10 = 0.50; baseline total 4.288957; changed total 3.462264 |
| documentaggregate単位で一緒に読むdataを保持する候補。 | baseline (480×4 + 360×5 + 48×2 + 90×4) / 978 = 4.269939; changed (24×4 + 360×5 + 480×2 + 90×4) / 954 = 3.371069; weight 0.45 | rating 3 × 0.20 = 0.60 | rating 4 × 0.15 = 0.60 | rating 3 × 0.10 = 0.30 | rating 3 × 0.10 = 0.30; baseline total 3.721472; changed total 3.316981 |
| key-valuekeyによるbounded lookupを中心にする候補。 | baseline (480×2 + 360×4 + 48×5 + 90×2) / 978 = 2.883436; changed (24×2 + 360×4 + 480×5 + 90×2) / 954 = 4.264151; weight 0.45 | rating 2 × 0.20 = 0.40 | rating 5 × 0.15 = 0.75 | rating 2 × 0.10 = 0.20 | rating 4 × 0.10 = 0.40; baseline total 3.047546; changed total 3.668868 |
changed inputはfrequencyだけで、query-fit ratingは固定され、導出されるaccess-fit rating、weighted score、winnerが順に変わり得ることを説明できる。
動く例で考える
顧客中心から商品・期間中心へADRを更新する
- 前提
- 現在500,000注文、月50,000件増、12か月予測のsynthetic workloadを使う。baselineでは顧客履歴が毎時480回の主要queryであり、商品・期間検索は毎時48回ある。
- 入力
- 4つのquery shape、3つの不変条件、2つの更新競合と、合計weight 1.0の5 criteriaを入力する。三モデルには同じcriteria IDで1〜5のratingを与える。
- 操作
- 各queryのfrequencyとmodel別query fitの積からaccess fitを加重平均し、他criteriaのratingとweightを合計してbaseline winnerをADRへ記録する。その後、顧客履歴を毎時24回、商品・期間検索を毎時480回へ変え、同じ式で全optionのscoreを再計算する。
- 観測
- baselineではrelationalが最大scoreとなる。変更後は固定された商品・期間keyのread modelに適合するkey-valueが最大となり、winnerが変わる。constraint、hot key、再構築、dual-read照合は未解決の運用責任として残る。
- 結論
- 変更後winnerを即時置換の指示にしない。source of truth、secondary read model、backfill、dual-read差分、rollback、restore演習をADR confirmationにして段階的に検証する。
次のPython 3.13 harnessは標準libraryの固定値だけを使い、network、secret、shell、filesystemへアクセスしない。access pattern mutationを同じcriteriaと計算式で再評価する。
python3.13 - <<'PY'
import json
HARNESS = "storage_decision_lab_v1"
QUERY_FREQUENCIES = {
"get_customer_history": 480,
"get_order_detail": 360,
"list_product_window": 48,
"update_order_state": 90,
}
QUERY_DEFINITIONS = [
{
"id": "get_customer_history",
"accessed_together": ["order_id", "placed_at", "total", "status"],
},
{
"id": "get_order_detail",
"accessed_together": ["order", "line_items", "payment_summary"],
},
{
"id": "list_product_window",
"accessed_together": ["product_id", "placed_at", "order_id", "quantity"],
},
{
"id": "update_order_state",
"accessed_together": ["order_id", "current_state", "next_state", "version"],
},
]
WORKLOAD = {
"query_shapes": [
{
**definition,
"frequency_per_hour": QUERY_FREQUENCIES[definition["id"]],
}
for definition in QUERY_DEFINITIONS
],
"invariants": [
"order_id is unique",
"order total equals the sum of line item amounts",
"order state follows the allowed transition graph",
],
"update_conflicts": [
"payment confirmation and cancellation update the same order version",
"shipment allocation and line cancellation update overlapping line items",
],
}
GROWTH_INPUTS = {
"current_records": 500_000,
"monthly_growth": 50_000,
"months": 12,
}
GROWTH_FORECAST = {
**GROWTH_INPUTS,
"projected_records": (
GROWTH_INPUTS["current_records"]
+ GROWTH_INPUTS["monthly_growth"] * GROWTH_INPUTS["months"]
),
"evidence_kind": "synthetic-projection",
}
CRITERIA = {
"access_fit": {
"weight": 0.45,
"question": "Do primary query shapes have bounded read fan-out?",
},
"constraint_coverage": {
"weight": 0.20,
"question": "Where are uniqueness, references, and transitions enforced?",
},
"capacity": {
"weight": 0.15,
"question": "Does the model have a tested path to projected volume?",
},
"operations": {
"weight": 0.10,
"question": "Can the team migrate, observe, and operate it?",
},
"recovery": {
"weight": 0.10,
"question": "Can backup, restore, and reconstruction meet the target?",
},
}
# Query fit stays independent of frequency so every workload mutation is
# recomputed through one auditable weighted average instead of a scenario table.
QUERY_FIT_RATINGS = {
"relational": {
"get_customer_history": 5,
"get_order_detail": 3,
"list_product_window": 1,
"update_order_state": 4,
},
"document": {
"get_customer_history": 4,
"get_order_detail": 5,
"list_product_window": 2,
"update_order_state": 4,
},
"key-value": {
"get_customer_history": 2,
"get_order_detail": 4,
"list_product_window": 5,
"update_order_state": 2,
},
}
BASELINE_INPUTS = [
{
"id": "relational",
"ratings": {
"constraint_coverage": 5,
"capacity": 4,
"operations": 4,
"recovery": 5,
},
"constraint_evidence": [
"synthetic DDL maps order uniqueness, references, and state checks",
],
"capacity_evidence": [
"projected 1.1M rows fit the lab capacity envelope; measure indexes in production",
],
"operations_evidence": [
"team rehearsal covers online index creation and reversible migration",
],
"recovery_evidence": [
"point-in-time restore and constraint validation are confirmation steps",
],
},
{
"id": "document",
"ratings": {
"constraint_coverage": 3,
"capacity": 4,
"operations": 3,
"recovery": 3,
},
"constraint_evidence": [
"document validation covers shape; cross-document invariants need explicit ownership",
],
"capacity_evidence": [
"order documents need growth and index-size limits before adoption",
],
"operations_evidence": [
"schema versioning and rolling migration are not yet rehearsed",
],
"recovery_evidence": [
"restore must also validate denormalized fields and index rebuild",
],
},
{
"id": "key-value",
"ratings": {
"constraint_coverage": 2,
"capacity": 5,
"operations": 2,
"recovery": 4,
},
"constraint_evidence": [
"single-key conditions are local; multi-key invariants need a tested protocol",
],
"capacity_evidence": [
"synthetic partition distribution stays bounded at projected volume",
],
"operations_evidence": [
"secondary access path rebuild and hot-key response are not rehearsed",
],
"recovery_evidence": [
"reconstruction is deterministic but requires source snapshot retention",
],
},
]
def rating_inputs_for(workload):
return {
query["id"]: query["frequency_per_hour"]
for query in workload["query_shapes"]
}
def score_options(option_inputs, workload):
rating_inputs = rating_inputs_for(workload)
total_frequency = sum(rating_inputs.values())
options = []
for option_input in option_inputs:
query_fit_ratings = dict(QUERY_FIT_RATINGS[option_input["id"]])
query_contributions = {
query_id: frequency * query_fit_ratings[query_id]
for query_id, frequency in rating_inputs.items()
}
access_fit = sum(query_contributions.values()) / total_frequency
ratings = {
**option_input["ratings"],
"access_fit": access_fit,
}
score = sum(
rating * CRITERIA[criterion_id]["weight"]
for criterion_id, rating in ratings.items()
)
options.append(
{
**option_input,
"ratings": dict(ratings),
"query_fit_ratings": query_fit_ratings,
"query_contributions": query_contributions,
"score": score,
}
)
return options
def selected_option(options):
return max(options, key=lambda option: option["score"])["id"]
def evaluate_workload(workload):
# A shared evaluator prevents baseline and transfer scenarios from drifting
# into different scoring rules while still exposing every derived value.
rating_inputs = rating_inputs_for(workload)
dominant_query_id = max(rating_inputs, key=rating_inputs.__getitem__)
options = score_options(BASELINE_INPUTS, workload)
return {
"workload": workload,
"rating_inputs": rating_inputs,
"dominant_query_id": dominant_query_id,
"workload_profile": f"{dominant_query_id}-dominant",
"derived_access_fit_ratings": {
option["id"]: option["ratings"]["access_fit"]
for option in options
},
"options": options,
"selected_option": selected_option(options),
}
def mutate_access_pattern(workload):
changed_frequencies = {
**{
query["id"]: query["frequency_per_hour"]
for query in workload["query_shapes"]
},
"get_customer_history": 24,
"list_product_window": 480,
}
return {
**workload,
"query_shapes": [
{
**query,
"frequency_per_hour": changed_frequencies[query["id"]],
}
for query in workload["query_shapes"]
],
}
def mutate_order_detail(workload):
changed_frequencies = {
**rating_inputs_for(workload),
"get_order_detail": 3_600_000,
}
return {
**workload,
"query_shapes": [
{
**query,
"frequency_per_hour": changed_frequencies[query["id"]],
}
for query in workload["query_shapes"]
],
}
query_ids = [query["id"] for query in WORKLOAD["query_shapes"]]
assert len(query_ids) == len(set(query_ids))
assert all(
query["frequency_per_hour"] > 0 and query["accessed_together"]
for query in WORKLOAD["query_shapes"]
)
assert abs(sum(item["weight"] for item in CRITERIA.values()) - 1.0) < 1e-9
assert GROWTH_FORECAST["projected_records"] == (
GROWTH_FORECAST["current_records"]
+ GROWTH_FORECAST["monthly_growth"] * GROWTH_FORECAST["months"]
)
assert all(
set(query_ids) == set(query_fit_ratings)
for query_fit_ratings in QUERY_FIT_RATINGS.values()
)
baseline = evaluate_workload(WORKLOAD)
mutated_workload = mutate_access_pattern(WORKLOAD)
access_pattern_mutation = evaluate_workload(mutated_workload)
order_detail_mutation = evaluate_workload(mutate_order_detail(WORKLOAD))
assert baseline["dominant_query_id"] == "get_customer_history"
assert baseline["selected_option"] == "relational"
assert access_pattern_mutation["dominant_query_id"] == "list_product_window"
assert access_pattern_mutation["selected_option"] == "key-value"
assert (
access_pattern_mutation["selected_option"]
!= baseline["selected_option"]
)
assert order_detail_mutation["dominant_query_id"] == "get_order_detail"
assert (
order_detail_mutation["selected_option"] == "document"
), "order-detail-causal-invariant"
assert next(
query["frequency_per_hour"]
for query in mutated_workload["query_shapes"]
if query["id"] == "list_product_window"
) == 480
report = {
"fixture": "order-history-workload-v1",
"fixture_metadata": {
"kind": "synthetic",
"provenance": "lesson-authored deterministic order-history scenario",
"limitations": [
"not a production query trace",
"not a vendor benchmark or availability claim",
"ratings require replacement with measured workload and restore evidence",
],
},
"workload": WORKLOAD,
"growth_forecast": GROWTH_FORECAST,
"criteria": CRITERIA,
"baseline": baseline,
"adr": {
"status": "accepted-for-lab",
"context": "customer-history queries dominate the synthetic baseline",
"decision": baseline["selected_option"],
"negative_consequences": [
"secondary indexes add write and migration cost",
"joins and index growth require measured latency and capacity evidence",
],
"confirmation": [
"validate invariants with generated conflicting updates",
"measure query latency at projected records",
"complete backup restore and reversible migration rehearsal",
],
},
"access_pattern_mutation": {
**access_pattern_mutation,
"changed_assumption": "access-pattern",
"decision_recomputed": (
access_pattern_mutation["selected_option"]
!= baseline["selected_option"]
),
"migration_evidence": [
"backfill the product-window read model from an immutable snapshot",
"compare dual-read result sets before routing traffic",
"retain relational source and replay cursor for rollback",
],
},
"order_detail_mutation": {
**order_detail_mutation,
"changed_assumption": "dominant-query",
"decision_recomputed": (
order_detail_mutation["selected_option"]
!= baseline["selected_option"]
),
},
"vendor_scope": {
"PostgreSQL": "18",
"MongoDB": "8.0",
"Dynamo": "2007-paper-model",
},
"mastery_evidence": {
"lab_steps": [
{"step": 1, "evidence": "four unique query shapes, invariants, and update conflicts"},
{"step": 2, "evidence": "synthetic 12-month projected record calculation"},
{"step": 3, "evidence": "five weighted criteria and evidence for all three models"},
{"step": 4, "evidence": "computed baseline scores, winner, and ADR confirmation"},
{"step": 5, "evidence": "mutated workload, recomputed ratings, winner, and migration evidence"},
],
"assessments": [
{"assessment": 1, "evidence": "document growth, conflict, constraint, index, and restore rebuttal"},
{"assessment": 2, "evidence": "frequency, score, dual-read, backfill, and rollback update"},
],
"rubric_dimensions": [
"technical-correctness",
"judgment",
"evidence",
"communication",
],
"transfer": {
"task": "注文検索が顧客中心から商品・期間中心へ変わるアクセスパターン変更でストレージADRを再評価する",
"changed_assumption": "access-pattern",
"evidence": "winner changes only after workload and all option scores are recomputed",
},
},
"external_network_used": False,
}
print(json.dumps(report, ensure_ascii=False, sort_keys=True))
PY
トレードオフと失敗モード
- 誤診: document databaseならschema設計もmigrationも不要である。反証: 保存documentのfield、型、version、validation、reader互換性は変化する。古いversionを含むfixtureでrolling migrationとrollbackを実行し、未知fieldと欠損fieldの扱いを観測する。
- 誤診: key-valueのsingle-tableへ統合すれば、すべてのaccess patternが常に最速になる。反証: 既知key以外のqueryは二次index、fan-out、複製read modelが必要になりうる。queryごとのread数、hot key分布、write amplification、再構築時間を測る。
- 誤診: relational tableをpartitionすれば、成長後の全queryが自動的に速くなる。反証: predicateがpartition keyを絞らなければpruningできず、partition数やglobal constraintが運用費を増やす。実query planと分位latency、maintenance時間を変更前後で比較する。
- constraintの局所化: 一つのaggregateで守れる不変条件と、複数record・keyをまたぐ不変条件を分ける。後者をapplicationへ移すなら、競合時のprotocolとreconciliationをtestする。
- 容量の錯覚: 総record数だけではhot partition、index size、document肥大、write burstを説明できない。分布と増加率を含む複数scenarioで安全余裕を決める。
- 運用・回復: backupが存在することと、要求時間内に整合した状態へrestoreできることは別である。schema version、secondary index、read modelを含めて定期的に復旧を演習する。
知識チェック
- product名を挙げる前に、query shapeへ最低限どの情報を付けるべきか。
- relational、document、key-valueを同じcriteriaとweightで比べる理由は何か。
- document modelで「schemaがない」という主張を、どのartifactとtestで反証できるか。
- projected recordsが同じでも、query分布の変更でwinnerが変わるのはなぜか。
- 商品・期間read modelへ移す際、backfill完了だけを成功条件にできない理由は何か。
- ADR confirmationへconstraint、capacity、operations、recoveryの証拠を一つずつ挙げる。
出典と次の学習
relational modelの原理はCoddの論文、PostgreSQL固有のdata definitionはversion 18の公式資料、document modelingはMongoDB 8.0の公式資料、key-valueの機構は2007年Dynamo論文で確認する。本文中に外部resourceを埋め込まず、完全なtitle、URL、kindはlesson metadataへ分離した。
次は「トランザクション・分離・整合性」で、同じ不変条件が並行実行時にどう破れるかを決定的scheduleで再現する。その後「性能・容量設計」でsynthetic projectionを負荷曲線、bottleneck証拠、安全容量へ接続する。
復習: 1日後にquery shapeと不変条件、7日後に三モデルの比較、30日後にaccess pattern mutation、90日後に実projectのADRを同じcriteriaで再評価する。
実践ラボ
注文履歴workloadから三モデルのADRを更新する
提出成果物: アクセスパターン、整合性、成長予測を含むストレージADR
- synthetic注文履歴fixtureについて、一意なquery shape ID、頻度、同時に読むdata、不変条件、更新競合を記述する
- 現在件数、月間増加、予測月数からprojected recordsを計算し、synthetic projectionの由来と限界を記録する
- access fit、constraint coverage、capacity、operations、recoveryのweightを固定し、relational・document・key-valueへ観測可能なrating evidenceを付ける
- 同じcriteriaから三案の加重score、winner、負の帰結、confirmationを計算し、ストレージADRを生成する
- 注文検索を顧客中心から商品・期間中心へ変更し、query frequency、rating、winner、移行・回復条件を再計算する
説明して理解を確かめる
6分で、document databaseがschema-lessではない理由、single-table設計が常に勝つわけではない理由、partition追加だけで全queryが速くならない理由を、query shape、不変条件、運用、回復の証拠から説明する。
アセスメント
問い: 注文detailを一つのdocumentへ埋め込めばjoinが消えて常に速い、という提案をどう評価するか。
期待する証拠: 同時に読むdata、document成長上限、部分更新競合、重複、constraint、index、実測latency、回復手順による反証
問い: 商品・期間中心の検索頻度が顧客履歴を上回った。既存ADRのどこを更新し、移行をどう安全に確認するか。
期待する証拠: access-pattern前提、query frequency、同一criteriaのratingとscore、winner、dual-read照合、backfill、rollback、confirmationの再計算
別問題へ転用する
注文検索が顧客中心から商品・期間中心へ変わるアクセスパターン変更でストレージADRを再評価する
復習スケジュール
- 1日後
query shape一つについて、同時に読むdata、頻度、不変条件、更新競合を列挙する
- 7日後
三モデルを同じcriteriaで比べ、最有力案の負の帰結とconfirmationを示す
- 30日後
access pattern変更でratingとwinnerを再計算し、移行とrollback条件を説明する
- 90日後
query shape一つについて、同時に読むdata、頻度、不変条件、更新競合を列挙する
評価ルーブリック
| 観点 | 未達 | 発展途上 | 熟達 | 卓越 |
|---|---|---|---|---|
| technical-correctness | 製品カテゴリだけを選び、query shape、不変条件、更新競合を記述しない | アクセスパターンはあるが、成長予測またはconstraint・回復境界が抜ける | query shape、不変条件、更新競合、成長予測を論理modelと物理配置へ対応付ける | index、partition key、二次access path、移行中の整合性、復旧演習まで検証可能にする |
| judgment | schema-less、join不要、single-tableなど一つの標語だけで選ぶ | 複数案は比較するが、criteriaのweightやrating evidenceが揃わない | 三案を同じ基準で比較し、負の帰結と前提変更時の再評価条件を残す | 可逆性、teamの運用能力、vendor差、将来の不確実性を段階移行へ反映する |
| evidence | benchmark名や平均値だけを示し、fixtureとworkloadが不明である | synthetic結果はあるが、由来、限界、constraint、recoveryの証拠が不足する | 固定fixtureからquery、成長、score、winner、ADRを決定的に再生成する | production query trace、容量傾向、restore演習を匿名化し、ADR confirmationへ継続接続する |
| communication | 製品名だけを結論にし、対象versionと判断範囲を示さない | 選択理由はあるが、代替案、負の帰結、owner、再評価条件が曖昧である | 前提、criteria、選択肢、score、帰結、confirmation、vendor scopeを一つのADRで追える | domain・開発・運用・security担当が同じ証拠から移行と撤退条件を合意できる |
出典
以下の外部資料は利用者が選択したときだけ開きます。
- A Relational Model of Data for Large Shared Data Banks (peer-reviewed)
- Chapter 5. Data Definition (primary)
- Data Modeling in MongoDB (primary)
- Dynamo: Amazon’s Highly Available Key-value Store (peer-reviewed)