# DynamoDB deep dive: access patterns, financial authority, and recovery

This chapter designs DynamoDB from explicit questions and invariants, not from
an entity diagram or a one-table slogan. Alex DeBrie's method is the primary
modeling authority: enumerate access patterns, shape item collections and
indexes to answer them, then account for the operational trade-offs. The
original Dynamo paper and the DynamoDB USENIX paper explain the distributed
systems lineage; current AWS documentation controls mutable API behavior. The
local SSE notes are prompts only and are corrected where they overreach.

The case used below is an intended repair target for the repository, not a
claim that its current tables already implement this design. Canonical defects
remain in [the repository evidence map](../research/repository-evidence.md).

## Access-pattern inventory: contract before schema

**Inference:** Every proposed key below must answer one of these stable IDs. `P95` rates are
design inputs for a workload test, not AWS guarantees. Size includes names,
types, keys, and projected attributes—not just the JSON payload. Key conditions
select the narrow set; filters may only reduce the returned set after read work
has occurred (C81; A111, F21,F22,F33–F35, retrieved 2026-08-22).

| AP | Operation | Key predicates | Sort, filter, order | Consistency / freshness | Cardinality and item size | Peak rate | Owner | Invariant | Failure response |
|---|---|---|---|---|---|---:|---|---|---|
| AP01 | get order | tenant+orderId | exact/no filter | strong command;eventual history | 1x1.2KiB | 2000 read/s | Orders | tenant+version | retry/reconcile by ID |
| AP02 | list account orders | tenant+account+time | descending/no filter | eventual<=2s | 100x0.45KiB/page | 1200 query/s | Orders | deterministic order;not snapshot | continue LEK/surface lag |
| AP03 | list status orders | tenant+account+status+time | descending/sparse | eventual<=5s | active subset 0.35KiB | 400 query/s | Orders | terminal membership removed | verify base before action |
| AP04 | read order trades | tenant+orderId | ascending/no filter | strong for remaining qty | 1-20x0.9KiB | 1500 query/s | Execution | fills<=accepted qty | stop/reconcile |
| AP05 | list account trades | tenant+account+time | descending/no filter | eventual<=2s | 100x0.5KiB/page | 1000 query/s | Execution | fill resolves to source | paginate/repair |
| AP06 | list symbol trades | tenant+symbol+time | descending/no filter | eventual<=5s | 100x0.4KiB/page;1page/request | 2000 query/s;top share 8% | Execution | not sequencer | formula selects shard/merge cursor |
| AP07 | read portfolio view | tenant+account+optional symbol | lexical/no filter | eventual<=2s;rebuildable | 10-5000x0.8KiB | 3000 read/s | Portfolio projection | sourceVersion+gap visible | never authorize;rebuild |
| AP08 | claim request | tenant+client key | exact/no filter | strong immediate | 1x0.7KiB | 2000 write/s | Orders API | fingerprint one result | complete/in-progress/mismatch |
| AP09 | claim event inbox | consumer+eventId+shardVersion | exact/no filter | strong transactional | 2x<=1KiB (0.35KiB receipt+protected state) | 5000 write/s | Consumer | one effect/event | no-op/read/repair |
| AP10 | append ledger txn | tenant+account+month+bucket | ascending append | transactional | 2-20x0.6KiB;max2m/month;250k/bucket | 1500 tx/s | Ledger | balanced immutable | reject/reconcile |
| AP11 | read ledger | tenant+account+month+buckets | merge ascending/no filter | strong investigation | max2m/month;8x<=150000KiB | 300 query/s | Ledger | complete history | paginate bucket cursor/control totals |
| AP12 | authorize cash/securities balance+reservation | tenant+account+asset(currency-or-symbol) | exact/version | strong transactional | 1x0.8KiB | 2000 tx/s | Ledger/Risk command | available policy+version | conflict retry;never projection |
| AP13 | projection checkpoint | projection+source key+shardVersion | exact/version | strong | 1x0.3KiB | 5000 write/s | Projection | next once/gap visible | park/replay/rebuild |
| AP14 | create/read/list reconciliation batches | tenant+date;optional batchId | BATCH#batchId exact/prefix | strong transitions | 1-20KiB/batch | 20 batch/s | Finance Ops | manifest+window | fail/restart idempotently |
| AP15 | list due reconciliation discrepancies | tenant+status+shard;due<=cutoff | dueAt#batchId#id range | eventual discover;strong base resolve | 0-millionsx0.5KiB | 200 query/s | Finance Ops | owned resolution | paginate/escalate/conditional resolve |
| AP16 | create/claim/complete/retry outbox | eventId base;service+shard;due<=cutoff | base exact;READY GSI due order | eventual discover;strong conditional transitions | 1x1.6KiB+0.45KiB READY index | create5000/s;claim5100/s;complete5000/s;retry100/s;move20/s | Producer | state+event atomic;one owner | lease/retry/reconcile |
| AP17 | client order uniqueness | tenant+clientOrderId | exact/no filter | transactional condition | 1x0.2KiB | 2000 write/s | Orders | one ID mapping | return/reject mismatch |
| AP18 | idempotency repair queue | tenant+shard+due | due order/sparse | eventual discover;strong takeover | rare 0.3KiB | 100 query/s | Orders Ops | one repair lease | inspect/complete/escalate |

The inventory deliberately separates command-current reads from eventual
listing projections. A GSI cannot satisfy a strong-read requirement; an AP that
authorizes a financial action therefore returns to the base item or ledger
authority (C82; A19,A100, retrieved 2026-08-22).

**Inference:** AP07 is only a stale-tolerant portfolio projection. Even a
strongly consistent read of that derived table would make only the stored copy
current; it would not make the copy authoritative. Cash, securities quantities,
and reservations used by risk or order admission are AP12 state owned by the
Ledger/Risk command boundary; AP07 carries source versions/gaps and rebuilds
from authority (C46,C91; A19,A32–A35, retrieved 2026-08-22; F08,F17,F21).

## Key and index design with AP traceability

**Inference:** The bounded-context table is the working default here because it
aligns service ownership and recovery while retaining one-request item
collections inside a context (C94; F21,F22). It is not one table per entity:
Orders, Execution, Portfolio, Ledger, Reconciliation, and consumer-owned Inbox
tables each overload their base `PK/SK` only within an owned vocabulary.

| Path | Kind | Table | PK | SK | Role | Read consequence | AP IDs |
|---|---|---|---|---|---|---|---|
| IDX01 | BASE | Orders | TEN#t#ORDER#id | META | aggregate root | strong/eventual | AP01 |
| IDX02 | GSI1 | Orders | TEN#t#ACCT#a | ORD#at#id | projected summary | eventual | AP02 |
| IDX03 | GSI2 | Orders | TEN#t#ACCT#a#STATUS#s | ORD#at#id | sparse moved membership | eventual | AP03 |
| IDX04 | BASE | Execution | TEN#t#ORDER#id | TRADE#at#id | item collection | strong/eventual | AP04 |
| IDX05 | GSI1 | Execution | TEN#t#ACCT#a | TRADE#at#id | projected summary | eventual | AP05 |
| IDX06 | GSI2 | Execution | TEN#t#SYM#symbol | TRADE#at#id | unsharded at 800RRU/s top-key assumption | eventual | AP06 |
| IDX07 | BASE | Portfolio | TEN#t#ACCT#a | POS#symbol | rebuildable view collection | eventual only | AP07 |
| IDX08 | BASE | Idempotency | TEN#t#REQ#key | REQUEST | state/result | strong | AP08 |
| IDX09 | BASE | Inbox | CONSUMER#name#V#v#SHARD#h(eventId)%13 | EVENT#id | deterministic 13-shard dedupe receipt | strong transactional | AP09 |
| IDX10 | BASE | Ledger | TEN#t#ACCT#a#MONTH#yyyy-mm#BKT#nn | POST#at#txn#line | sequence-range sub-bucket | strong/eventual | AP10,AP11 |
| IDX11 | BASE | Ledger | TEN#t#ACCT#a | AUTH#CASH#currency or AUTH#SEC#symbol | cash/securities authority | strong | AP12 |
| IDX12 | BASE | Projection | PROJ#name#V#v#SHARD#h(sourceKey)%7 | SOURCE#key | deterministic 7-shard checkpoint/gap | strong | AP13 |
| IDX13 | BASE | Reconciliation | TEN#t#RECON#date | BATCH#batchId | batch list/exact | strong | AP14 |
| IDX14 | BASE | Reconciliation | TEN#t#RECON#batchId | BREAK#id | discrepancy authority | strong | AP15 |
| IDX15 | GSI1 | Reconciliation | TEN#t#BREAK#status#shard | DUE#at#batch#id | sparse cross-batch due queue | eventual | AP15 |
| IDX16 | BASE | Context | OUTBOX#eventId | EVENT | semantic envelope | strong | AP16 |
| IDX17 | GSI3 | Context | SERVICE#svc#READY#shard | DUE#at#eventId | sparse 13-shard READY due queue | eventual | AP16 |
| IDX18 | BASE | Orders | TEN#t#CLIENTORDER#id | UNIQUE | sentinel | transactional | AP17 |
| IDX19 | GSI1 | Idempotency | TEN#t#REPAIR#shard | DUE#at#key | sparse repair | eventual+strong takeover | AP18 |

**Inference:** No LSI is proposed. The order/fill and account/position item collections are
bounded or period-bucketed where they can grow. An LSI would freeze an alternate
sort key at table creation and impose the current 10 GB item-collection limit;
a GSI is operationally independent but eventually consistent and can throttle
base writes when underprovisioned or hot (C86; A104, retrieved 2026-08-22).

The adjacency-list technique is justified only for true one-to-many reads:
AP04 order→fills and AP07 account→portfolio-view positions. Symbol and account histories use
GSIs because duplicating all reverse edges as base items would increase
transaction size and ownership ambiguity. All tenant-bearing partition keys
place tenant identity first for policy review but keep the naturally
high-cardinality order/account/symbol identifier; a low-cardinality status is
never the sole partition key.

### Machine-readable AP, path, and amplification contract

The verifier treats this block as the canonical model and cross-checks every
field in the three rendered tables. `base` and `index` are already-rounded write
units per logical operation; only transaction-targeted base-item writes use `base-multiplier=2`.
Asynchronous GSI entries always use `index-multiplier=1`. Storage consequences
are called out even when the operation is read-only.

<!-- TASK6_MODEL_BEGIN -->
```text
AP|AP01|get order|tenant+orderId|exact/no filter|strong command;eventual history|1x1.2KiB|2000 read/s|Orders|tenant+version|retry/reconcile by ID
AP|AP02|list account orders|tenant+account+time|descending/no filter|eventual<=2s|100x0.45KiB/page|1200 query/s|Orders|deterministic order;not snapshot|continue LEK/surface lag
AP|AP03|list status orders|tenant+account+status+time|descending/sparse|eventual<=5s|active subset 0.35KiB|400 query/s|Orders|terminal membership removed|verify base before action
AP|AP04|read order trades|tenant+orderId|ascending/no filter|strong for remaining qty|1-20x0.9KiB|1500 query/s|Execution|fills<=accepted qty|stop/reconcile
AP|AP05|list account trades|tenant+account+time|descending/no filter|eventual<=2s|100x0.5KiB/page|1000 query/s|Execution|fill resolves to source|paginate/repair
AP|AP06|list symbol trades|tenant+symbol+time|descending/no filter|eventual<=5s|100x0.4KiB/page;1page/request|2000 query/s;top share 8%|Execution|not sequencer|formula selects shard/merge cursor
AP|AP07|read portfolio view|tenant+account+optional symbol|lexical/no filter|eventual<=2s;rebuildable|10-5000x0.8KiB|3000 read/s|Portfolio projection|sourceVersion+gap visible|never authorize;rebuild
AP|AP08|claim request|tenant+client key|exact/no filter|strong immediate|1x0.7KiB|2000 write/s|Orders API|fingerprint one result|complete/in-progress/mismatch
AP|AP09|claim event inbox|consumer+eventId+shardVersion|exact/no filter|strong transactional|2x<=1KiB (0.35KiB receipt+protected state)|5000 write/s|Consumer|one effect/event|no-op/read/repair
AP|AP10|append ledger txn|tenant+account+month+bucket|ascending append|transactional|2-20x0.6KiB;max2m/month;250k/bucket|1500 tx/s|Ledger|balanced immutable|reject/reconcile
AP|AP11|read ledger|tenant+account+month+buckets|merge ascending/no filter|strong investigation|max2m/month;8x<=150000KiB|300 query/s|Ledger|complete history|paginate bucket cursor/control totals
AP|AP12|authorize cash/securities balance+reservation|tenant+account+asset(currency-or-symbol)|exact/version|strong transactional|1x0.8KiB|2000 tx/s|Ledger/Risk command|available policy+version|conflict retry;never projection
AP|AP13|projection checkpoint|projection+source key+shardVersion|exact/version|strong|1x0.3KiB|5000 write/s|Projection|next once/gap visible|park/replay/rebuild
AP|AP14|create/read/list reconciliation batches|tenant+date;optional batchId|BATCH#batchId exact/prefix|strong transitions|1-20KiB/batch|20 batch/s|Finance Ops|manifest+window|fail/restart idempotently
AP|AP15|list due reconciliation discrepancies|tenant+status+shard;due<=cutoff|dueAt#batchId#id range|eventual discover;strong base resolve|0-millionsx0.5KiB|200 query/s|Finance Ops|owned resolution|paginate/escalate/conditional resolve
AP|AP16|create/claim/complete/retry outbox|eventId base;service+shard;due<=cutoff|base exact;READY GSI due order|eventual discover;strong conditional transitions|1x1.6KiB+0.45KiB READY index|create5000/s;claim5100/s;complete5000/s;retry100/s;move20/s|Producer|state+event atomic;one owner|lease/retry/reconcile
AP|AP17|client order uniqueness|tenant+clientOrderId|exact/no filter|transactional condition|1x0.2KiB|2000 write/s|Orders|one ID mapping|return/reject mismatch
AP|AP18|idempotency repair queue|tenant+shard+due|due order/sparse|eventual discover;strong takeover|rare 0.3KiB|100 query/s|Orders Ops|one repair lease|inspect/complete/escalate
INDEX|IDX01|BASE|Orders|TEN#t#ORDER#id|META|aggregate root|strong/eventual|AP01
INDEX|IDX02|GSI1|Orders|TEN#t#ACCT#a|ORD#at#id|projected summary|eventual|AP02
INDEX|IDX03|GSI2|Orders|TEN#t#ACCT#a#STATUS#s|ORD#at#id|sparse moved membership|eventual|AP03
INDEX|IDX04|BASE|Execution|TEN#t#ORDER#id|TRADE#at#id|item collection|strong/eventual|AP04
INDEX|IDX05|GSI1|Execution|TEN#t#ACCT#a|TRADE#at#id|projected summary|eventual|AP05
INDEX|IDX06|GSI2|Execution|TEN#t#SYM#symbol|TRADE#at#id|unsharded at 800RRU/s top-key assumption|eventual|AP06
INDEX|IDX07|BASE|Portfolio|TEN#t#ACCT#a|POS#symbol|rebuildable view collection|eventual only|AP07
INDEX|IDX08|BASE|Idempotency|TEN#t#REQ#key|REQUEST|state/result|strong|AP08
INDEX|IDX09|BASE|Inbox|CONSUMER#name#V#v#SHARD#h(eventId)%13|EVENT#id|deterministic 13-shard dedupe receipt|strong transactional|AP09
INDEX|IDX10|BASE|Ledger|TEN#t#ACCT#a#MONTH#yyyy-mm#BKT#nn|POST#at#txn#line|sequence-range sub-bucket|strong/eventual|AP10,AP11
INDEX|IDX11|BASE|Ledger|TEN#t#ACCT#a|AUTH#CASH#currency or AUTH#SEC#symbol|cash/securities authority|strong|AP12
INDEX|IDX12|BASE|Projection|PROJ#name#V#v#SHARD#h(sourceKey)%7|SOURCE#key|deterministic 7-shard checkpoint/gap|strong|AP13
INDEX|IDX13|BASE|Reconciliation|TEN#t#RECON#date|BATCH#batchId|batch list/exact|strong|AP14
INDEX|IDX14|BASE|Reconciliation|TEN#t#RECON#batchId|BREAK#id|discrepancy authority|strong|AP15
INDEX|IDX15|GSI1|Reconciliation|TEN#t#BREAK#status#shard|DUE#at#batch#id|sparse cross-batch due queue|eventual|AP15
INDEX|IDX16|BASE|Context|OUTBOX#eventId|EVENT|semantic envelope|strong|AP16
INDEX|IDX17|GSI3|Context|SERVICE#svc#READY#shard|DUE#at#eventId|sparse 13-shard READY due queue|eventual|AP16
INDEX|IDX18|BASE|Orders|TEN#t#CLIENTORDER#id|UNIQUE|sentinel|transactional|AP17
INDEX|IDX19|GSI1|Idempotency|TEN#t#REPAIR#shard|DUE#at#key|sparse repair|eventual+strong takeover|AP18
MAP|AP01|IDX01
MAP|AP02|IDX02
MAP|AP03|IDX03
MAP|AP04|IDX04
MAP|AP05|IDX05
MAP|AP06|IDX06
MAP|AP07|IDX07
MAP|AP08|IDX08
MAP|AP09|IDX09
MAP|AP10|IDX10
MAP|AP11|IDX10
MAP|AP12|IDX11
MAP|AP13|IDX12
MAP|AP14|IDX13
MAP|AP15|IDX14,IDX15
MAP|AP16|IDX16,IDX17
MAP|AP17|IDX18
MAP|AP18|IDX19
LANE|L01|AP01|IDX01|2000 read/s|READ|2000|1|NA|NA|2400|NA|LOAD_TEST_REQUIRED|max-order share is not measured
LANE|L02|AP02|IDX02|1200 query/s|READ|1200|6|NA|NA|2400|NA|LOAD_TEST_REQUIRED|max-account share is not measured
LANE|L03|AP03|IDX03|400 query/s|READ|400|0.5|NA|NA|2400|NA|LOAD_TEST_REQUIRED|evaluated page and max account-status share are not measured
LANE|L04|AP04|IDX04|1500 query/s|READ|1500|5|NA|NA|2400|NA|LOAD_TEST_REQUIRED|max-order share and fill-count mix are not measured
LANE|L05|AP05|IDX05|1000 query/s|READ|1000|6.5|NA|NA|2400|NA|LOAD_TEST_REQUIRED|max-account share is not measured
LANE|L06|AP06|IDX06|2000 query/s;top share 8%|READ|2000|5|0.08|1|2400|800.0|FEASIBLE_PLANNING|explicit top-symbol share and one-page assumption
LANE|L07|AP07|IDX07|3000 read/s|READ|3000|0.5|NA|NA|2400|NA|LOAD_TEST_REQUIRED|optional-symbol versus whole-account page mix is not measured
LANE|L08|AP08|IDX08|2000 write/s|WRITE|2000|2|NA|NA|800|NA|LOAD_TEST_REQUIRED|max-client-key lane and lifecycle mix are not measured
LANE|L09A|AP09|IDX09|5000 write/s|WRITE|5000|2|1|13|800|769.23|FEASIBLE_PLANNING|fixed consumer scope hashes eventId over versioned shards
LANE|L09B|AP09|PROTECTED_STATE_AP_REQUIRED|5000 write/s|WRITE|5000|2|NA|NA|800|NA|UNPROVEN|consumer must bind protected state to its own registered AP lane
LANE|L10|AP10|IDX10|1500 tx/s|WRITE|1500|6|NA|NA|800|NA|LOAD_TEST_REQUIRED|AMP07 three-item scenario; max-account-month share and posting-count mix are not measured
LANE|L11|AP11|IDX10|300 query/s|READ|300|1|NA|NA|2400|NA|LOAD_TEST_REQUIRED|max-account share and evaluated page bytes are not measured
LANE|L12|AP12|IDX11|2000 tx/s|WRITE|2000|2|NA|NA|800|NA|LOAD_TEST_REQUIRED|max-account-asset share is not measured
LANE|L13|AP13|IDX12|5000 write/s|WRITE|5000|1|1|7|800|714.29|FEASIBLE_PLANNING|fixed projection scope hashes sourceKey over versioned shards
LANE|L14|AP14|IDX13|20 batch/s|WRITE|20|20|NA|NA|800|NA|LOAD_TEST_REQUIRED|read-write mix and hottest tenant-date share are not measured
LANE|L15A|AP15|IDX14|200 query/s|READ|200|0.5|NA|NA|2400|NA|LOAD_TEST_REQUIRED|base-resolution and page-byte mix are not measured
LANE|L15B|AP15|IDX15|200 query/s|READ|200|0.5|NA|NA|2400|NA|LOAD_TEST_REQUIRED|status-shard count and hottest due-queue share are not measured
LANE|L16A|AP16|IDX16|create5000/s;claim5100/s;complete5000/s;retry100/s;move20/s|WRITE|40440|1|NA|NA|800|NA|LOAD_TEST_REQUIRED|base work spans event keys but max physical-key placement is not measured
LANE|L16B|AP16|IDX17|create5000/s;claim5100/s;complete5000/s;retry100/s;move20/s|WRITE|10240|1|1|13|800|787.69|FEASIBLE_PLANNING|full READY lifecycle hashes eventId over thirteen shards
LANE|L17|AP17|IDX18|2000 write/s|WRITE|2000|2|NA|NA|800|NA|LOAD_TEST_REQUIRED|max-tenant-client-order share is not measured
LANE|L18|AP18|IDX19|100 query/s|READ|100|0.5|NA|NA|2400|NA|LOAD_TEST_REQUIRED|repair shard count and hottest due-queue share are not measured
AMP|AMP01|AP01,AP02|order create|2|1|1|1|3|base+account GSI
AMP|AMP02|AP03|order status key move|2|1|2|1|4|old delete+new put;lag
AMP|AMP03|AP04,AP05,AP06|trade+two GSIs|1|1|2|1|3|three representations
AMP|AMP04|AP07|portfolio view update|1|1|0|1|1|derived storage
AMP|AMP05|AP08,AP17|request+sentinel transaction|2|2|0|1|4|two targeted base items
AMP|AMP06|AP09|inbox receipt+protected state|2|2|0|1|4|atomic receipt+effect
AMP|AMP07|AP10,AP12|two postings+authority transaction|3|2|0|1|6|immutable ledger growth
AMP|AMP08|AP11|ledger read only|0|1|0|1|0|bucket pagination/storage
AMP|AMP09|AP13|checkpoint update|1|1|0|1|1|version state
AMP|AMP10|AP14|batch metadata|1|1|0|1|1|manifest storage
AMP|AMP11|AP15|discrepancy+due GSI|1|1|1|1|2|base authority+eventual queue
AMP|AMP12|AP16|outbox create+READY insert|2|2|1|1|5|transactional base+async insert
AMP|AMP13|AP18|failed state+repair GSI|1|1|1|1|2|sparse until repaired
AMP|AMP14|AP01,AP02,AP03,AP08,AP16,AP17|accepted_order_v1 complete route|6|2|3|1|15|4 base targets+3 async GSI entries
AMP|AMP15|AP16|READY claim+index removal|2|1|1|1|3|ordinary base update+async delete
AMP|AMP16|AP16|IN_FLIGHT completion|2|1|0|1|2|ordinary base update;no index entry
AMP|AMP17|AP16|IN_FLIGHT retry+READY insert|2|1|1|1|3|ordinary base update+async insert
AMP|AMP18|AP16|READY due/shard reschedule|2|1|2|1|4|ordinary base update+index delete+put
```
<!-- TASK6_MODEL_END -->

| Amplification | AP IDs | Scenario | Rounded base units | Base multiplier | Rounded GSI units | GSI multiplier | Total units | Consequence |
|---|---|---|---:|---:|---:|---:|---:|---|
| AMP01 | AP01,AP02 | order create | 2 | 1 | 1 | 1 | 3 | base+account GSI |
| AMP02 | AP03 | order status key move | 2 | 1 | 2 | 1 | 4 | old delete+new put;lag |
| AMP03 | AP04,AP05,AP06 | trade+two GSIs | 1 | 1 | 2 | 1 | 3 | three representations |
| AMP04 | AP07 | portfolio view update | 1 | 1 | 0 | 1 | 1 | derived storage |
| AMP05 | AP08,AP17 | request+sentinel transaction | 2 | 2 | 0 | 1 | 4 | two targeted base items |
| AMP06 | AP09 | inbox receipt+protected state | 2 | 2 | 0 | 1 | 4 | atomic receipt+effect |
| AMP07 | AP10,AP12 | two postings+authority transaction | 3 | 2 | 0 | 1 | 6 | immutable ledger growth |
| AMP08 | AP11 | ledger read only | 0 | 1 | 0 | 1 | 0 | bucket pagination/storage |
| AMP09 | AP13 | checkpoint update | 1 | 1 | 0 | 1 | 1 | version state |
| AMP10 | AP14 | batch metadata | 1 | 1 | 0 | 1 | 1 | manifest storage |
| AMP11 | AP15 | discrepancy+due GSI | 1 | 1 | 1 | 1 | 2 | base authority+eventual queue |
| AMP12 | AP16 | outbox create+READY insert | 2 | 2 | 1 | 1 | 5 | transactional base+async insert |
| AMP13 | AP18 | failed state+repair GSI | 1 | 1 | 1 | 1 | 2 | sparse until repaired |
| AMP14 | AP01,AP02,AP03,AP08,AP16,AP17 | accepted_order_v1 complete route | 6 | 2 | 3 | 1 | 15 | 4 base targets+3 async GSI entries |
| AMP15 | AP16 | READY claim+index removal | 2 | 1 | 1 | 1 | 3 | ordinary base update+async delete |
| AMP16 | AP16 | IN_FLIGHT completion | 2 | 1 | 0 | 1 | 2 | ordinary base update;no index entry |
| AMP17 | AP16 | IN_FLIGHT retry+READY insert | 2 | 1 | 1 | 1 | 3 | ordinary base update+async insert |
| AMP18 | AP16 | READY due/shard reschedule | 2 | 1 | 2 | 1 | 4 | ordinary base update+index delete+put |

The lane contract below is deliberately conservative. It covers every AP and
every mapped base/index route. A numeric `FEASIBLE_PLANNING` row has an
independently recomputed `rate * rounded-units * max-key-share / shards` result;
an unmeasured distribution remains `LOAD_TEST_REQUIRED` or `UNPROVEN` rather
than borrowing table-average or adaptive-capacity headroom. `NA` therefore
means “the interview design still needs a key histogram/load test,” not zero.

| Lane | AP | Route | AP peak-rate contract | Unit kind | Rate/s | Rounded units/op | Max-key share | Shards | Target units/s | Load/shard | State | Distribution evidence |
|---|---|---|---|---|---:|---:|---:|---:|---:|---:|---|---|
| L01 | AP01 | IDX01 | 2000 read/s | READ | 2000 | 1 | NA | NA | 2400 | NA | LOAD_TEST_REQUIRED | max-order share is not measured |
| L02 | AP02 | IDX02 | 1200 query/s | READ | 1200 | 6 | NA | NA | 2400 | NA | LOAD_TEST_REQUIRED | max-account share is not measured |
| L03 | AP03 | IDX03 | 400 query/s | READ | 400 | 0.5 | NA | NA | 2400 | NA | LOAD_TEST_REQUIRED | evaluated page and max account-status share are not measured |
| L04 | AP04 | IDX04 | 1500 query/s | READ | 1500 | 5 | NA | NA | 2400 | NA | LOAD_TEST_REQUIRED | max-order share and fill-count mix are not measured |
| L05 | AP05 | IDX05 | 1000 query/s | READ | 1000 | 6.5 | NA | NA | 2400 | NA | LOAD_TEST_REQUIRED | max-account share is not measured |
| L06 | AP06 | IDX06 | 2000 query/s;top share 8% | READ | 2000 | 5 | 0.08 | 1 | 2400 | 800.0 | FEASIBLE_PLANNING | explicit top-symbol share and one-page assumption |
| L07 | AP07 | IDX07 | 3000 read/s | READ | 3000 | 0.5 | NA | NA | 2400 | NA | LOAD_TEST_REQUIRED | optional-symbol versus whole-account page mix is not measured |
| L08 | AP08 | IDX08 | 2000 write/s | WRITE | 2000 | 2 | NA | NA | 800 | NA | LOAD_TEST_REQUIRED | max-client-key lane and lifecycle mix are not measured |
| L09A | AP09 | IDX09 | 5000 write/s | WRITE | 5000 | 2 | 1 | 13 | 800 | 769.23 | FEASIBLE_PLANNING | fixed consumer scope hashes eventId over versioned shards |
| L09B | AP09 | PROTECTED_STATE_AP_REQUIRED | 5000 write/s | WRITE | 5000 | 2 | NA | NA | 800 | NA | UNPROVEN | consumer must bind protected state to its own registered AP lane |
| L10 | AP10 | IDX10 | 1500 tx/s | WRITE | 1500 | 6 | NA | NA | 800 | NA | LOAD_TEST_REQUIRED | AMP07 three-item scenario; max-account-month share and posting-count mix are not measured |
| L11 | AP11 | IDX10 | 300 query/s | READ | 300 | 1 | NA | NA | 2400 | NA | LOAD_TEST_REQUIRED | max-account share and evaluated page bytes are not measured |
| L12 | AP12 | IDX11 | 2000 tx/s | WRITE | 2000 | 2 | NA | NA | 800 | NA | LOAD_TEST_REQUIRED | max-account-asset share is not measured |
| L13 | AP13 | IDX12 | 5000 write/s | WRITE | 5000 | 1 | 1 | 7 | 800 | 714.29 | FEASIBLE_PLANNING | fixed projection scope hashes sourceKey over versioned shards |
| L14 | AP14 | IDX13 | 20 batch/s | WRITE | 20 | 20 | NA | NA | 800 | NA | LOAD_TEST_REQUIRED | read-write mix and hottest tenant-date share are not measured |
| L15A | AP15 | IDX14 | 200 query/s | READ | 200 | 0.5 | NA | NA | 2400 | NA | LOAD_TEST_REQUIRED | base-resolution and page-byte mix are not measured |
| L15B | AP15 | IDX15 | 200 query/s | READ | 200 | 0.5 | NA | NA | 2400 | NA | LOAD_TEST_REQUIRED | status-shard count and hottest due-queue share are not measured |
| L16A | AP16 | IDX16 | create5000/s;claim5100/s;complete5000/s;retry100/s;move20/s | WRITE | 40440 | 1 | NA | NA | 800 | NA | LOAD_TEST_REQUIRED | base work spans event keys but max physical-key placement is not measured |
| L16B | AP16 | IDX17 | create5000/s;claim5100/s;complete5000/s;retry100/s;move20/s | WRITE | 10240 | 1 | 1 | 13 | 800 | 787.69 | FEASIBLE_PLANNING | full READY lifecycle hashes eventId over thirteen shards |
| L17 | AP17 | IDX18 | 2000 write/s | WRITE | 2000 | 2 | NA | NA | 800 | NA | LOAD_TEST_REQUIRED | max-tenant-client-order share is not measured |
| L18 | AP18 | IDX19 | 100 query/s | READ | 100 | 0.5 | NA | NA | 2400 | NA | LOAD_TEST_REQUIRED | repair shard count and hottest due-queue share are not measured |

## Table-boundary decision: three defensible shapes

**Inference:** Single-table design's strongest benefit is not “fewer tables.” It is answering
a heterogeneous, related access pattern in one request. The cost is that shared
key/index vocabulary becomes an API among teams, and an unforeseen query may
require reshaping or asynchronous projection. Start with APs and ownership, not
doctrine (C81; A111, F21,F22,F33–F35, retrieved 2026-08-22).

| Dimension | One mega-table | Single table per bounded context | Multiple entity tables |
|---|---|---|---|
| Request shape | Excellent for cross-entity item collections with one owner | One-request aggregates within Orders, Execution, or Ledger | Batch/transaction or application composition across entities |
| Deployment coupling | Shared index/key changes coordinate every writer | Context team coordinates only its vocabulary | Lowest schema coupling; more orchestration |
| Transaction scope | Same-table is convenient but not uniquely capable | Cross-context transaction is available inside the current same-account, same-Region boundary | Same current cross-table boundary; more table names and IAM resources |
| Ownership / IAM | Broad table permissions and shared stream are hard to isolate | Clear owner, narrow IAM, dedicated stream/alarms | Clearest entity IAM; ownership can become too granular |
| Blast radius / scaling | One hot index, restore, or bad deploy affects all contexts | Context-local capacity, hot keys, restore, and alarms | Smallest table blast radius; more fleet overhead |
| Backup / restore | Restoring one context means restoring a new copy of everything and extracting | Restore context table and rebuild only its projections | Fine-grained restores; cross-table recovery needs a manifest/cutover |
| Observability | Table averages hide context/AP attribution unless every request is tagged | AP metrics align to one service and indexes | Clear table metrics but many dashboards |
| Streams | One mixed change feed requires type filtering and shared evolution | Context-owned CDC/outbox relay | Multiple event-source mappings and checkpoints |
| Cognitive complexity | Highest global key/index grammar | Moderate, cohesive grammar per domain | Simple schemas; complex cross-table workflows |
| Best fit | One team, stable related APs, high value from mixed item collection | Several domain owners with rich aggregates | Independent entities, distinct lifecycle/security/scale, relational composition elsewhere |

`TransactWriteItems` and `TransactGetItems` currently accept up to 100 distinct
items and 4 MB across one or more tables in the same account and Region; a
write transaction cannot target the same item twice. Indexes themselves are
not transaction targets. This means “transactions require one DynamoDB table”
is false, although cross-Region/cross-account coordination remains outside this
boundary (C83; A20,A101, retrieved 2026-08-22).

**Inference:** The bounded-context table is the selected shape for this case,
but keep a decision record. It minimizes shared deployment/IAM/Stream/restore
blast radius while preserving AP04 and AP07 item collections; use a cross-table
transaction only when a truly synchronous invariant spans owned tables and its
100-item/4 MB ceiling is acceptable (C94; F21,F22).

## Concurrency, transactions, uniqueness, and durable idempotency

### Conditions and versions

**Inference:** Use `attribute_not_exists(PK)` for deterministic creation and a condition such
as `version = :expected AND state = :allowed` for compare-and-set transitions.
Return the new version as part of the command result. Optimistic locking stops
a stale writer; it does not recognize that two different messages represent
the same business event. An inbox receipt supplies that identity boundary
(C93; A101,A110, retrieved 2026-08-22).

**Inference:** For AP17, transactionally condition-create
`TEN#t#CLIENTORDER#clientOrderId / UNIQUE` with its mapped `orderId` and create
the order. A concurrent claimant loses the condition and then reads the
sentinel: same fingerprint returns the existing order, different fingerprint
is a conflict. A pre-read followed by an unconditional write has a race (C93;
A101,A110, retrieved 2026-08-22).

Transactions consume capacity even when canceled, and their changes propagate
to GSIs, Streams, backups, and exports gradually. `ClientRequestToken` gives
identical `TransactWriteItems` calls a ten-minute idempotency window; altered
parameters cause a mismatch. That API feature is useful for a short retry but
is not the permanent record of an accepted order (C83; A20,A101, retrieved 2026-08-22).

### AP08 durable request state machine

**Inference:** Store the following record, not merely a cached `201` response
(C90; A20,A101, retrieved 2026-08-22; F11):

```text
PK = TEN#<tenant>#REQ#<clientKey>, SK = REQUEST
fingerprint = SHA-256(canonical command fields + tenant + operation + schema version)
state = IN_PROGRESS | COMPLETED | REPAIR_REQUIRED
attemptOwner, leaseUntil, createdAt, updatedAt
result = {orderId, acceptedVersion, responseCode, responseDigest}
effect = {orderId, transactionId, outboxEventId}
expiresAt = cleanup time only
```

1. Canonicalize the command and hash it. Conditionally create `IN_PROGRESS`
   with a short lease; never use the TTL timestamp as the lease decision.
2. A same-key/different-fingerprint request is `409 Conflict`. It must not
   inherit another command's result.
3. A concurrent same-fingerprint duplicate reads strongly: `COMPLETED` returns
   the durable result; a live `IN_PROGRESS` returns retry guidance; an expired
   lease can be conditionally taken over by one owner.
4. In one transaction, validate the lease/fingerprint, write order and
   uniqueness sentinel, write the semantic outbox item, and set `COMPLETED`
   with the durable response. If table boundaries make one transaction
   unsuitable, make the authoritative effect deterministically discoverable
   and move to `REPAIR_REQUIRED`; never pretend a partial dual write completed.
5. The repair worker strongly rereads the record and protected effect. It
   completes from evidence, safely reattempts a deterministic absent effect,
   or escalates an ambiguous external effect. TTL may later remove a record
   only after the business replay/audit horizon.

**Inference:** AP09 applies the same principle at each consumer: target the
receipt and the consumer's protected state in one `TransactWriteItems` call.
The receipt key is
`CONSUMER#name#V#v#SHARD#h(businessEventId)%13 / EVENT#businessEventId`;
both sub-1-KiB base items therefore cost `2 * 2 = 4` WRU/event in this planning
case. The receipt lane alone costs two WRU/event, or 10,000 WRU/s at 5,000
events/s; `ceil(10000/(1000*0.80))=13` gives 769.23 WRU/s/shard. The protected
state does not magically share that receipt distribution: its table/key must
name another AP-specific lane, histogram, and capacity proof. Store source
aggregate/version and effect receipt. Transport message IDs are diagnostics,
not the durable business identity (C90,C87; A20,A22,A101,A105, retrieved
2026-08-22; F11,F24,F32,F33).

Do not change `N` in place. Here `h` is a contract, not a runtime hash: use the
unsigned first 64 bits of SHA-256 over the canonical UTF-8 business key, and
store `hashVersion` with `shardMapVersion`. A shard-map version selects the hash for each new
business event; retries retain the original version. During cutover, route
lookups by recorded version (or check the bounded old/new versions), stop old
admission at a declared watermark, drain and reconcile both maps, and retain
old receipts through the deduplication horizon. The cost is version metadata,
dual-version reads during cutover, and operational reconciliation; the benefit
is a deterministic capacity argument rather than a hope that opaque
split-for-heat/adaptive behavior will hold the 80% target.

## Money, ledger authority, projections, and reconciliation

The application currently moves JavaScript `number` values through order,
trade, and portfolio paths. Authoritative financial amounts must not use
binary floating point. Choose one representation per field:

- fixed minor units as an integer plus ISO currency and explicit scale; or
- DynamoDB `Number` serialized/deserialized with an exact decimal library,
  never through a binary-float intermediate.

Define an explicit rounding policy: rounding mode, rounding point, precision, fee/tax allocation, currency
conversion rate identity, and residual handling in the contract. “Two decimal
places” is not enough for instruments, quantities, FX, or currencies with
different scales (C47; A41, F08,F17, retrieved 2026-08-22).

**Inference:** A ledger transaction uses one deterministic `txnId`; immutable
posting rows carry account, currency, signed exact amount, effective/recorded
times, source business ID, and posting line. Validate that debits and credits
balance by currency before a transaction writes all postings and conditionally
updates the versioned AP12 `AUTH#CASH#currency` or `AUTH#SEC#symbol`
authority/reservations. DynamoDB supplies
conditional/transactional storage; the application supplies chart-of-accounts,
balancing, authorization, immutability, rounding, and reversal semantics (C91;
F08,F17,F21).

Never update a posting. Correct an error with a linked reversing/correcting
transaction. A current balance is a compact authority only when its version
and control total are updated with postings; a portfolio, P&L, OpenSearch
document, or cache remains derived. Reservation state distinguishes posted,
reserved, and available amounts so two commands cannot spend one balance.

**Inference:** AP13 projection records enforce source version `n = current+1`.
`n <= current` is a duplicate/stale no-op; `n > current+1` records a gap and
parks the update. Reconciliation compares source order/fill IDs, ledger posting
IDs, balances, and projection versions for a declared window, emits AP15
discrepancies, and requires evidence before a conditional resolution. This is
the correctness backstop; DynamoDB alone does not supply accounting semantics
(C91; F08,F17,F21).

**Inference:** The checkpoint PK is
`PROJ#name#V#v#SHARD#h(sourceKey)%7 / SOURCE#sourceKey`. At 5,000 ordinary
sub-1-KiB conditional writes/s, `N=ceil(5000/(1000*0.80))=7`, or 714.29
WRU/s/shard. Hashing the stable `sourceKey` preserves next-version ordering for
that source; it deliberately does not promise one total order across unrelated
sources. Whole-projection scans/listing now fan out seven keyed queries and
merge source watermarks. Reshard with the same versioned map, admission
watermark, drain, gap/control-total reconciliation, and conditional cutover
used for AP09; retries must not silently switch versions (C87,C95;
A22,A100,A105, retrieved 2026-08-22; F24,F32,F33).

## DynamoDB Streams and semantic outbox events

**Inference:** Treat Streams as short-lived CDC. Current retention is 24 hours;
ordering covers mutations to each individual item primary key, not every item
in a partition-key collection or a business transaction. View choices are
`KEYS_ONLY`, `NEW_IMAGE`, `OLD_IMAGE`, and `NEW_AND_OLD_IMAGES`; choose the
smallest view that still supports repair; `NEW_AND_OLD_IMAGES` supplies old and new images.
Lambda delivery is at-least-once and may
process a record repeatedly (C84; A21,A102, retrieved 2026-08-22).

Transaction item changes can arrive at different times and interleave with
other changes. A consumer therefore must not infer one business event by
grouping nearby order/sentinel/idempotency records. `NEW_AND_OLD_IMAGES` helps
derive transitions but increases record size and still does not supply a
semantic envelope (C83; A101, retrieved 2026-08-22).

**Inference:** AP16 writes one explicit outbox event item in the same local
transaction as the accepted state. It contains stable `eventId`, aggregate ID
and version, event type/schema version, occurred time, trace/causation IDs, and
the consumer contract payload. Its exact state machine is:

1. `ABSENT -> READY`: the transaction creates the base item with `readyPk`,
   `readySk=DUE#dueAt#eventId`, attempt zero, and no lease; the sparse GSI gets
   one insert.
2. `READY -> IN_FLIGHT`: a conditional claim checks state/version and an absent
   or expired lease, sets owner/lease/attempt, and removes both READY key
   attributes; the GSI gets one delete.
3. `IN_FLIGHT -> COMPLETED`: only the lease owner conditionally stores the
   durable broker acknowledgement and completion time; READY attributes are
   already absent, so the GSI has no action.
4. `IN_FLIGHT -> READY`: a failed publish or expired-lease repair conditionally
   clears owner/lease, records the failure, chooses a new `dueAt`, and restores
   READY attributes; the GSI gets one insert.
5. `READY -> READY`: a deliberate due/shard reschedule conditionally changes an
   indexed key, so GSI maintenance is one old-key delete plus one new-key put.

**Inference:** Every stale/repeated transition condition-fails and reads the durable state;
an ambiguous broker acknowledgement can therefore repeat publication, and
consumers still use AP09. This repairs the dual-write gap without claiming
end-to-end exactly once. The current GSI accounting for a sub-1-KiB entry is one
put when indexed attributes appear, one delete when they disappear, and
delete+put when an indexed key changes (C84,C95; A21,A102,A104, retrieved
2026-08-22).

Checkpointing within the 24-hour window supports ordinary retry, not indefinite
rebuild. A projection that falls behind retention needs a PITR-backed export or
another versioned full source, followed by catch-up and a measured cutover.
Keep unpublished outbox items longer than the maximum outage/replay policy; the
Stream is not the durable event archive.

## Capacity, skew, and auditable amplification

The full formulas and mutation-tested inputs are in the
[shared cost model](../research/cost-model.md#dynamodb-capacity-and-amplification-model).
Current rounding is API-specific: standard writes round each item to 1 KiB;
standard strong reads round evaluated bytes to 4 KiB, eventual reads use half,
and each transaction-targeted base-item write uses two underlying write operations.
Indexes are not transaction targets; asynchronous GSI maintenance adds ordinary
index write units after commit. `Query` consumption is
based on evaluated bytes before filtering; `BatchGetItem` rounds each item
separately (C82,C83; A100,A101,A104, retrieved 2026-08-22).

<!-- TASK6_CHAPTER_CAPACITY_RENDER_BEGIN -->
| Result ID | Result | Consequence |
|---|---:|---|
| standard_write_units | 2 | 1.2KiB crosses two 1KiB buckets |
| base_plus_index_units | 3 | base2+ordinary GSI1 |
| moved_index_units | 4 | base2+old delete1+new put1 |
| strong_read_units | 3 | 10KiB/4KiB rounded |
| eventual_read_units | 1.5 | half standard strong |
| transactional_read_units | 6 | two underlying reads |
| query_eventual_units | 3 | 24,000B evaluated before filter |
| batch_eventual_units | 1.5 | each item rounds separately |
| accepted_order_v1_base_txn_units | 12 | four targeted base items |
| accepted_order_v1_gsi_units | 3 | two order GSIs+READY GSI ordinary cost |
| accepted_order_v1_total_units | 15 | 12 base transaction+3 async GSI |
| ap06_page_kib | 40 | one 100x0.4KiB page/request |
| ap06_eventual_rru_per_page | 5 | half of ten 4KiB chunks |
| ap06_aggregate_rru_s | 10000 | 2000 requests/s x5 |
| ap06_top_symbol_rru_s | 800 | planning top share 8% |
| ap06_shards | 1 | ceil(800/(3000x0.80)) |
| ap09_atomic_path_wru_per_event | 4 | two transaction-targeted <=1KiB base items x2 |
| ap09_receipt_wru_per_event | 2 | receipt item alone uses transaction multiplier |
| ap09_receipt_aggregate_wru_s | 10000 | 5000 receipt writes/s x2 |
| ap09_receipt_shards | 13 | ceil(10000/(1000x0.80)) |
| ap09_receipt_per_shard_wru_s | 769.23 | 10000/13 |
| ap09_receipt_headroom_percent | 3.85 | below 800 WCU/s target |
| ap13_checkpoint_wru_per_event | 1 | ordinary conditional <=1KiB write |
| ap13_checkpoint_aggregate_wru_s | 5000 | 5000 checkpoint writes/s x1 |
| ap13_checkpoint_shards | 7 | ceil(5000/(1000x0.80)) |
| ap13_checkpoint_per_shard_wru_s | 714.29 | 5000/7 |
| ap13_checkpoint_headroom_percent | 10.71 | below 800 WCU/s target |
| ap16_create_base_wru_s | 20000 | 5000xceil(1.6KiB)x2 transaction ops |
| ap16_claim_base_wru_s | 10200 | 5100xceil(1.6KiB) ordinary update |
| ap16_complete_base_wru_s | 10000 | 5000xceil(1.6KiB) ordinary update |
| ap16_retry_base_wru_s | 200 | 100xceil(1.6KiB) ordinary update |
| ap16_reschedule_base_wru_s | 40 | 20xceil(1.6KiB) ordinary update |
| ap16_base_total_wru_s | 40440 | create+claim+complete+retry+reschedule |
| ap16_ready_create_insert_wru_s | 5000 | 5000 READY inserts x1 |
| ap16_ready_claim_delete_wru_s | 5100 | 5100 READY removals x1 |
| ap16_ready_retry_insert_wru_s | 100 | 100 retry READY inserts x1 |
| ap16_ready_reschedule_move_wru_s | 40 | 20 READY key moves x(delete+put) |
| ap16_no_retry_move_floor_index_wru_s | 10000 | 5000 create inserts+5000 claim deletes |
| ap16_ready_index_wru_s | 10240 | all sparse-index lifecycle actions |
| ap16_ready_shards | 13 | ceil(10240/(1000x0.80)) |
| ap16_per_shard_wru_s | 787.69 | 10240/13 |
| ap16_headroom_percent | 1.54 | below 800 WCU/s target |
| ap16_total_lifecycle_wru_s | 50680 | base40440+READY index10240 |
| ledger_month_kib | 1200000 | 2m postings x0.6KiB |
| ledger_sub_buckets | 8 | count and byte ceilings agree |
| ledger_bucket_max_mib | 146.48 | 150000KiB/1024 |
<!-- TASK6_CHAPTER_CAPACITY_RENDER_END -->

**Inference:** The named `accepted_order_v1` route includes the order,
idempotency result, outbox event, and AP17 sentinel as four base transaction
targets. Rounded base units are `2+1+2+1=6`; two operations/item make 12. The
order account/status GSIs and AP16 READY GSI add `1+1+1=3` ordinary units after
commit, totaling 15. Canceled transactions can still consume capacity, so
conditional-conflict load belongs in the peak model (C83,C95; A100,A101,A104,
retrieved 2026-08-22).

### Mode and skew decisions

**Inference:** Current on-demand tables start warm at 12,000 read units/s and 4,000 write
units/s. An individual physical partition remains bounded at 3,000 read units/s
and 1,000 write units/s. These are dated service dimensions, not a promise that
one partition key receives the whole table's warm throughput (C87; A22,A105,
retrieved 2026-08-22).

| Choice | Use when | Boundary to test |
|---|---|---|
| On-demand | uncertain/spiky traffic and willingness to pay its request shape | prior peak/warm throughput, sudden step, hot key, account/table quotas |
| Provisioned | predictable rate, reserved headroom, explicit cost/control | forecast error, target utilization, GSI capacity, scale-out delay |
| Autoscaling | gradual provisioned changes | CloudWatch/control-loop delay; not admission control |
| Warm throughput | known launch/failover rate above current warm value | pre-warm lead time and distribution; cannot cure one hot key |
| Burst/adaptive capacity | transient uneven demand with spare capacity | finite credits/partition mechanics; do not encode as guaranteed hot-key budget |

**Inference:** The lane table above is the decision surface: twenty-one physical-route rows
cover AP01–AP18. Four rows have sufficient planning inputs for numeric
feasibility (AP06, AP09 receipt, AP13 checkpoint, and AP16 READY); every other
route remains visibly `LOAD_TEST_REQUIRED` or `UNPROVEN`. A table can split an
item collection when current mechanics permit, but placement and timing are
service-managed. With no LSI proposed here, that behavior may help observed
traffic; it is still not the deterministic proof for an explicit 80% lane
target. Warm, burst, and adaptive capacity likewise do not replace the
per-route histogram and load test (C87; A22,A105, retrieved 2026-08-22;
F24,F32,F33).

**Inference:** Instrument `ConsumedReadCapacityUnits`,
`ConsumedWriteCapacityUnits`, throttled requests/reasons, latency, conditional
failures, transaction conflicts, GSI lag, item size, and contributor/key skew by
AP. Table averages cannot prove AP06's hottest symbol or AP12's hottest account
is healthy (C87; A22,A105, retrieved 2026-08-22).

**Inference:** AP06's explicit planning case is one 100-item page/request at
0.4 KiB/item. Five eventual read units/query times 2,000 queries/s is 10,000
units/s aggregate; an 8% top symbol is 800 units/s. With a 2,400-unit/s target,
`ceil(800/2400)=1`, so IDX06 stays unsharded. If measured top-symbol demand
exceeds the target, write sharding changes
`PK=SYM#X` into `PK=SYM#X#SHARD#h(eventId)%N`. That distributes writes but AP06
must fan out `N` Queries, merge-sort, deduplicate, paginate a compound cursor,
and surrender one-partition total order. Record the shard version in items and
perform an ordered cutover. Prefer a naturally high-cardinality account/order
key when it already meets rate and order requirements (C87; A22,A105,
retrieved 2026-08-22; F24,F32,F33).

**Inference:** AP16 hashes `eventId` over thirteen READY shards for a balanced
steady planning lifecycle: 5,000 creates/s enter READY; 5,100 claims/s remove
5,000 new plus 100 retried items; 5,000 items complete; 100 fail or expire back
to READY; and 20 already-READY items/s move due/shard keys. At 0.45 KiB/index
entry, the no-retry/no-move floor is 5,000 inserts + 5,000 deletes = 10,000
index units/s. Retry circulation adds 100 inserts and 100 claim-deletes;
rescheduling adds 20 deletes + 20 puts, totaling 10,240. Thus
`N=ceil(10240/(1000*0.80))=13`, 787.69 units/s/shard, and 1.54% headroom below
the 800-unit target. Base work remains separate: create 20,000, claim 10,200,
complete 10,000, retry 200, and reschedule 40 units/s, for 40,440 base and
50,680 base+index units/s. `dueAt` is monotonic only inside each shard; poll all
thirteen equality keys, merge by `(dueAt,eventId)`, and carry one LEK per shard
in the cursor. Recompute `N` before any measured rate/mix step (C87,C95;
A100,A104,A105, retrieved 2026-08-22).

**Inference:** AP10/AP11 impose a hard 2,000,000-posting/account/month admission
limit. At 0.6 KiB, that is 1,200,000 KiB. A deterministic posting-sequence
bucket rolls at 250,000 postings or 150,000 KiB, whichever comes first, so the
example uses eight buckets of at most 146.48 MiB. AP11 merge-paginates them with
one LEK/bucket; crossing the monthly cap rejects/escalates instead of silently
growing the key (C87,C95; A100,A104,A105, retrieved 2026-08-22).

## Lifecycle, recovery, and global operation

### Pagination, Query, Scan, and expiry

**Inference:** A `Query` requires partition-key equality and may narrow the sort key. Its
filter runs after evaluation. Continue while `LastEvaluatedKey` is present;
empty returned items plus a continuation key is possible after filtering. A
`Scan` reads broad table/index data and also paginates. Neither `ProjectionExpression`
nor `Select=COUNT` turns a broad read into a cheap keyed read (C81,C96; A111,
retrieved 2026-08-22; F34).

**Inference:** Online APIs use AP keys, never a table Scan. Bulk reconciliation
uses a PITR export with an immutable manifest; an emergency paginated Scan has
bounded parallelism, a captured checkpoint/window, throttling protection, and
restart state. This directly avoids the repository's single-page/in-memory
snapshot failure (C96; A106,A111, retrieved 2026-08-22).

**Inference:** TTL expiry is cleanup only and is asynchronous, typically within a few days. An
expired item can remain readable and billable until deletion; use `leaseUntil`
and a condition against current time for takeover/authorization, not item
absence. TTL deletions enter the originating Region's Stream as service deletes;
MREC replicas consume replicated writes and cannot identify the replicated
delete the same way. MRSC does not support TTL (C85; A23,A103,A107, retrieved 2026-08-22).

### Backup, export, restore, and projection rebuild

**Inference:** PITR currently retains a configurable 1–35 days at per-second granularity and
restores to a new table, with the latest restorable time approximately five
minutes behind. Export to S3 is asynchronous, uses the PITR data plane, and
does not consume RCUs or affect the table's performance. Full and incremental
exports are available; an incremental range is a compacted final-state view and
one transaction can be split across adjacent exports (C88; A106, retrieved 2026-08-22).

**Inference:** A recovery runbook therefore: (1) declares the source timestamp
and business freeze/cutover policy; (2) restores or exports to a new named
dataset; (3) validates counts, control totals, schema and projection version;
(4) rebuilds a new projection namespace; (5) catches up changes with stable
event/version IDs; (6) proves zero gaps/duplicates against reconciliation; and
(7) atomically changes the reader alias/config. Never overwrite the old
projection before proof. Tags, IAM, alarms, Streams, TTL, autoscaling, and
application configuration require explicit restore checks (C88,C96; A102,A106,
retrieved 2026-08-22).

### Global Tables: make the consistency mode explicit

| Boundary | MREC | MRSC |
|---|---|---|
| Default / availability | Current default; replicas in supported DynamoDB Regions (A107, retrieved 2026-08-22) | Introduced 2025; supported Region sets only (A107, retrieved 2026-08-22) |
| Topology | Multiple replicas (A107, retrieved 2026-08-22) | Exactly three Regions: three replicas or two replicas + witness (A107, retrieved 2026-08-22) |
| Replication/read | Asynchronous; monitor `ReplicationLatency` (A107, retrieved 2026-08-22) | Synchronous quorum; cross-Region strong reads, zero RPO; higher latency (A107, retrieved 2026-08-22) |
| Transactions | Atomic only in source Region; replicas can temporarily observe a partial transaction (A107, retrieved 2026-08-22) | Transaction APIs unsupported (A107, retrieved 2026-08-22) |
| TTL / LSI | TTL supported with replicated-delete cost (A107, retrieved 2026-08-22) | TTL and LSI unsupported (A107, retrieved 2026-08-22) |
| Streams | Replication uses Streams; per-item order and replica records can differ (A107, retrieved 2026-08-22) | Not enabled by default; if enabled replica records/order are identical (A107, retrieved 2026-08-22) |
| Change mode | Cannot change consistency mode after creation (A107, retrieved 2026-08-22) | Cannot change consistency mode after creation (A107, retrieved 2026-08-22) |

**Inference:** For authoritative balances, multi-active MREC writes can race and
last-writer convergence is not an accounting conflict policy. Prefer a home
Region/single-writer ownership with idempotent failover and reconciliation, or
use MRSC only after accepting its topology, latency, feature, transaction, and
external-effect boundaries. MRSC zero RPO for table data does not make a broker,
bank API, exchange acknowledgement, or customer notification atomic. Detect
conflicts/errors, preserve request/event identity, and reconcile every external
effect (C89; A24,A107, retrieved 2026-08-22).

## DAX, ElastiCache, Aurora, and OpenSearch by access pattern

**Inference:** These are complements or alternatives chosen by a concrete AP;
none changes which system owns a monetary invariant (C92; A32–A35,A108,A109,A112,
retrieved 2026-08-22).

| Candidate and AP | Query / transaction shape | Consistency and invalidation | Operational load | Source-of-truth boundary |
|---|---|---|---|---|
| DAX for repeated AP01 history reads | DynamoDB-compatible item reads; not arbitrary search | Eventually consistent reads cache; strong and transactional reads pass through. Cached Query/Scan results are not invalidated by item writes, so accept TTL staleness explicitly. (A108, retrieved 2026-08-22) | Cluster sizing, subnet/security, hit/miss/eviction and fallback | Never AP12 authority or AP04 transition decision |
| ElastiCache for computed session/market reference data | Application-defined keys, structures, atomic cache operations | Define cache-aside/read-through, TTL, invalidation message loss, stampede, and source fallback. (A34,A109, retrieved 2026-08-22) | Nodes/serverless capacity, failover, memory/eviction, client behavior | Cache disposable; DynamoDB/ledger or market source remains authoritative |
| Aurora PostgreSQL for ledger/reporting with evolving relations | PostgreSQL-compatible SQL and ACID transactions; Data API exposes begin/commit/rollback operations (A112, retrieved 2026-08-22) | Isolation/endpoint mode must be selected and tested; local write forwarding supports documented isolation levels but not `SERIALIZABLE`; replica freshness is separate (A35,A112, retrieved 2026-08-22) | Schema/index tuning, connections, failover, engine maintenance | **Inference:** Candidate ledger authority only if its accounting invariants and operating model are explicitly implemented (C92; A112, retrieved 2026-08-22) |
| OpenSearch for AP05/AP06/AP15 discovery | Full-text, relevance, aggregations, flexible filters | Async index with refresh/ingestion lag, duplicates and mapping evolution; rebuildable. (A32, retrieved 2026-08-22) | Domain/shard sizing, mappings, ingestion DLQ, rollover, snapshot | Search is a locator/projection; strong-read authority before action |

DAX is a poor response to the repository's `trades-reader`: the query is a GSI
history path, freshness matters, and cache invalidation is not established.
Measure reuse and acceptable staleness before paying cluster cost. ElastiCache
offers more flexible structures but more application-owned coherence. Aurora
is preferable when APs are genuinely relational/ad hoc and transaction
constraints dominate key-value scale. OpenSearch is preferable for search and
aggregations that would otherwise create proliferating GSIs—but only as a
rebuildable projection.

## Repository repair map

This is a repair design against the canonical evidence, not a second audit.
All locators and exact observations remain under
[CS01–CS12 in the repository evidence map](../research/repository-evidence.md#twelve-required-issue-routes).

| ID | DynamoDB-relevant repair | Proof / residual boundary |
|---|---|---|
| CS01 | Commit order, fingerprinted AP08 result, AP17 sentinel, and AP16 outbox item in one transaction. | Prove no accepted order lacks outbox; relay/consumer still repeat. |
| CS02 | Replace cached `201` with durable `IN_PROGRESS`/`COMPLETED` result, fingerprint mismatch rejection, lease, and repair state. | TTL is cleanup; ambiguous external effects still require evidence. |
| CS03 | Persist relay attempt and broker response per outbox ID; inspect every `PutEvents` entry and reconcile target effects. | Broker acknowledgement is not downstream completion. |
| CS04 | Atomically write fill, conditionally advance order version/remaining quantity, and write semantic trade outbox item. | Cross-boundary ledger/clearing effects need inbox/outbox/reconciliation. |
| CS05 | Retry discovers deterministic fill/outbox records and resumes publication; duplicate guard returns an effect receipt. | Never delete repair evidence merely because publish later succeeds. |
| CS06 | AP09 inbox event ID plus AP13 source version accompany the conditional AP07 portfolio projection update. | Version stops stale order; inbox stops duplicate identity; only AP12 authority may authorize cash/securities/risk. |
| CS07 | Accept only next aggregate version, expose gaps, park later events, and rebuild from full source. | EventBridge provides routing, not per-account causal order. |
| CS08 | Keep EventBridge target-DLQ policy and failed-DLQ-send metrics as separate transport controls. | DynamoDB repair records do not grant SQS policy or replace DLQ runbooks. |
| CS09 | Replace one-page Scan with PITR export, manifest/control totals, versioned rebuild, catch-up, and cutover. | Export is async and not an atomic business transaction snapshot. |
| CS10 | Store fixed minor units or exact decimals with currency, scale, and explicit rounding. | Validate serialization end to end; no binary-float intermediate. |
| CS11 | Add immutable balanced postings, versioned balances/reservations, AP14/AP15 reconciliation, and projections. | DynamoDB primitives do not invent accounting semantics. |
| CS12 | Keep matching/sequencing on an evidenced runtime; use DynamoDB for durable orders, results, ledger, and projections. | Durability does not establish deterministic microsecond matching. |

## Poor fits and myth corrections

### Poor fits

- A latency-critical central limit order book requiring deterministic global
  priority and one sequenced matching loop. DynamoDB can persist surrounding
  state; its partitioned request model is not the matching algorithm.
- Unbounded ad-hoc joins, constantly changing predicates, and exploratory
  analytics. Export to S3/Athena or use Aurora/OpenSearch according to the AP.
- Long-retention event sourcing from DynamoDB Streams. Retention is 24 hours;
  store an authoritative event sequence explicitly if that is the model.
- Cross-Region multi-item financial transactions with arbitrary external
  effects. Current MREC/MRSC boundaries do not supply that atomicity.
- One hot monotonically addressed key whose required write rate exceeds one
  partition and whose total order cannot be relaxed. Sharding changes the order
  contract; capacity mode does not repeal it.

### Myth and correction table

| Local-note myth or tempting shorthand | Correction |
|---|---|
| “Use a single table for DynamoDB.” | **Inference:** Start with APs. One table helps when a related heterogeneous collection answers them in one request; ownership/new-query/analytics trade-offs can justify other boundaries (C81; A111, F21,F22,F33–F35, retrieved 2026-08-22). |
| “Transactions lock items and are about 2–5 ms / 25 tx/s per item.” | Do not invent lock/latency/per-item throughput guarantees. Use the documented 100-item, 4 MB, same-account/Region scope and load-test the real shape (C83; A20,A101, retrieved 2026-08-22). |
| “`ClientRequestToken` gives business idempotency.” | It is a ten-minute identical-call window. AP08 persists fingerprint, state, lease, effect and result (C83; A20,A101, retrieved 2026-08-22). |
| “Strong reads cost 2×.” | Standard strong is one 4 KiB unit; eventual is half; transactional strong is twice standard (C82; A19,A100, retrieved 2026-08-22). |
| “A GSI can give current status if I request strong consistency.” | GSIs are eventual-only. Return to the base table/LSI for a strong decision (C82; A19,A100, retrieved 2026-08-22). |
| “A filter makes a Query cheap.” | **Inference:** Filtering occurs after evaluated read work. Put selectivity in keys (C81; A111, F34, retrieved 2026-08-22). |
| “Streams delivers every change exactly once and in table order.” | **Inference:** It is 24-hour CDC with per-item order; Lambda is at least once and repeats (C84; A21,A102, retrieved 2026-08-22). |
| “A transaction appears as one event in Streams.” | Item changes may interleave. Write one semantic outbox item in the transaction (C83; A101, retrieved 2026-08-22). |
| “TTL deletes within 48 hours, so it enforces expiry.” | **Inference:** Current wording is typically within a few days; conditions enforce expiry/lease, TTL only cleans up (C85; A23,A103, retrieved 2026-08-22). |
| “On-demand fixes hot keys.” | **Inference:** Warm/prior-peak and physical-partition limits remain; one partition is 3,000 read units/s and 1,000 write units/s (C87; A22,A105, retrieved 2026-08-22). |
| “DAX invalidates all queries on write.” | **Inference:** Strong/transaction reads pass through; cached Query/Scan results are not invalidated by item writes (C92; A108, retrieved 2026-08-22). |
| “Global Tables are always eventual.” | **Inference:** MREC is default; MRSC now provides cross-Region strong reads/zero RPO under strict limits (C89; A24,A107, retrieved 2026-08-22). |
| “A mutable balance row is a ledger.” | **Inference:** A ledger needs exact amounts, immutable balanced postings, authority, reversals, versions and reconciliation (C91; F08,F17,F21). |

## Senior decision table

| Decision | Choose | Reject / escalate when | Evidence before production |
|---|---|---|---|
| Table boundary | Bounded-context tables here | Cross-context AP misses latency/transaction budget | AP trace, IAM/restore/stream owner, load test |
| Base key | Natural high-cardinality tenant + order/account | Hot-key percentile breaches partition boundary | key histogram and per-AP consumed capacity |
| GSI | Only IDX02/03/05/06/14/15/17 | AP needs strong read or amplification lacks budget | bytes, lag, throttle and rebuild test |
| LSI | None here | Strong alternate order within bounded <10 GB collection is proven | growth proof and creation-time decision |
| Idempotency | Fingerprinted durable state/result + inbox | External effect cannot be observed/reconciled | concurrency/timeout/lease/repair game day |
| Transaction | One invariant, <=100 items/4 MB, same account/Region | MRSC/cross-Region/account/external API required | conflict load, capacity, cancellation evidence |
| Money/ledger | Exact amounts + immutable balanced postings | Rounding/reversal/authority undefined | invariant tests and reconciliation |
| Streams | CDC/outbox within 24-hour recovery | Long replay/event sourcing/transaction envelope required | repeat/gap/outage exercise |
| Capacity | On-demand uncertain; provisioned stable measured load | one hot key or untested step growth | peak/skew/item/index/unit model |
| Global | Regional/home-Region MREC; MRSC for stated need | feature/latency/topology limits fail | failover/conflict/reconciliation game day |
| Cache/search | Only stale-tolerant derived AP | used to authorize financial command | invalidation/lag/fallback/rebuild proof |
| Recovery | PITR/export → new version → validate → catch up → cut over | no manifest/control totals/rollback | timed restore and reconciliation |

## Interview follow-ups

### 1. Why enumerate APs before drawing keys?

**Rubric:** Name operation, predicates, order, consistency, size/rate, owner,
invariant and failure response; every key/index must trace back.

### 2. When is single-table design valuable?

**Rubric:** One request over heterogeneous related items; then discuss stable
APs, ownership, analytics, new-query friction and recovery coupling.

### 3. Why bounded-context tables here?

**Rubric:** Align ownership/blast radius while preserving local item
collections; acknowledge cross-table transactions and trade-offs.

### 4. Can a GSI satisfy a strongly current balance read?

**Rubric:** No; GSI is eventual-only. Use base/LSI strong read or transaction,
and name lag/fallback behavior.

### 5. What is the current transaction boundary?

**Rubric:** Up to 100 distinct items/4 MB, one or more tables, same account and
Region; no two write actions on one item; not indexes/external effects.

### 6. Why is `ClientRequestToken` insufficient?

**Rubric:** Ten-minute API window versus durable fingerprinted business
record/result, concurrent duplicates, leases, cleanup and repair.

### 7. Design idempotency through a timeout.

**Rubric:** Fingerprint, conditional `IN_PROGRESS`, owner/lease, protected
effect + `COMPLETED`, durable result, evidence-based takeover.

### 8. Does optimistic locking deduplicate events?

**Rubric:** No; version enforces order. Inbox identity deduplicates. Explain
duplicate, stale, next, and gap cases.

### 9. How do you enforce a unique client order ID?

**Rubric:** Sentinel plus entity in one conditioned transaction; on collision
read mapping and compare fingerprint, never check-then-put.

### 10. How do you represent money?

**Rubric:** Fixed minor units or exact Decimal/Number, currency/scale, rounding,
precision, conversion and residual policy; no binary float.

### 11. What makes the ledger authoritative?

**Rubric:** Immutable balanced postings, deterministic IDs, versioned
balances/reservations, reversals, exact amounts and reconciliation.

### 12. What order does DynamoDB Streams provide?

**Rubric:** Per individual item primary key, 24-hour retention; Lambda at least
once. Not collection/global/business-transaction order.

### 13. Why an outbox item if Streams exists?

**Rubric:** Transaction items can interleave; outbox supplies one semantic
envelope atomically with state. Relay and consumers still deduplicate.

### 14. Recompute acceptance write cost.

**Rubric:** Round four base targets, then `2*(2+1+2+1)=12`; add three
ordinary asynchronous GSI writes for `15`. Never double index entries.

### 15. Why can a selective filter be expensive?

**Rubric:** Read work precedes filter. Move selectivity into keys and continue
`LastEvaluatedKey` even after an empty filtered page.

### 16. Does on-demand solve a hot account?

**Rubric:** No. Separate warm/prior-peak behavior from the physical-partition
3,000 read-unit/1,000 write-unit boundary; measure skew.

### 17. Compare MREC and MRSC for balances.

**Rubric:** MREC default/async/conflicts/source-Region transaction; MRSC
three-Region quorum/strong/zero RPO/higher latency/no TTL-LSI-tx; external
effects remain separate.

### 18. How do you recover a projection after 30 hours?

**Rubric:** Streams expired. Use PITR/export manifest and versioned namespace,
catch-up with IDs/versions, prove gaps/control totals, then cut over.

## Sources used in this chapter

- Alex DeBrie: [*The DynamoDB Book*](https://www.dynamodbbook.com/) (F21),
  [single-table design](https://www.alexdebrie.com/posts/dynamodb-single-table/)
  (F22), [DynamoDB paper](https://www.alexdebrie.com/posts/dynamodb-paper/)
  (F33), [filters](https://www.alexdebrie.com/posts/dynamodb-filter-expressions/)
  (F34), and [one-to-many](https://www.alexdebrie.com/posts/dynamodb-one-to-many/)
  (F35).
- Foundations: [original Dynamo paper](https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf)
  (F32) and [DynamoDB USENIX paper](https://www.usenix.org/conference/atc22/presentation/elhemali)
  (F24).
- Current AWS docs, all retrieved 2026-08-22: [read consistency](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html)
  (A19), [transactions](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html)
  (A101), [capacity](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/read-write-operations.html)
  (A100), [indexes](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html)
  (A104), [warm throughput](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/warm-throughput-scenarios.html)
  (A105), [Streams](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html)
  (A102), [TTL](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html)
  (A103), [backup/export](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/S3DataExport.HowItWorks.html)
  (A106), [Global Tables](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-global-table-design.html)
  (A107), and [Query/pagination](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.Pagination.html)
  (A111).
- Adjacent services, retrieved 2026-08-22: [DAX](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DAX.consistency.html)
  (A108), [ElastiCache integration](https://docs.aws.amazon.com/prescriptive-guidance/latest/dynamodb-elasticache-integration/introduction.html)
  (A109), [Aurora](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Aurora.Replication.html)
  (A35), and [OpenSearch](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/what-is.html)
  (A32).
