Data model & keyspace¶
fdyno maps DynamoDB's data model onto FoundationDB's ordered key-value space using the directory and tuple layers. Each table gets its own directory; within it, every kind of record lives in a distinct subspace.
Keyspace layout¶
dynodb/<table>/
├── m → table metadata (JSON schema)
├── i/<hash>[/<range>] → item (binary codec)
├── c/<hash>[/<range>]/<chunk_idx> → large-item chunks (> 10 KB)
├── x/<index>/<hash>/<sort>/<basePK> → secondary index entry (GSI or LSI)
└── v/<versionstamp> → change-stream (CDC) record
flowchart LR
T["dynodb/<table>/"]
T --> M["m · metadata"]
T --> I["i · items"]
T --> C["c · large-item chunks"]
T --> X["x · secondary indexes"]
T --> V["v · change stream"]
Keys: the tuple layer¶
DynamoDB keys can be strings (S), numbers (N), or binary (B), and queries
must return them in the correct sorted order. fdyno encodes keys with
FoundationDB tuples, which are byte-ordered so that lexicographic comparison
of the encoded bytes matches DynamoDB's type-aware ordering, so keys need no
manual padding or delimiter escaping. A composite (hash + range) key becomes a
two-element tuple;
range queries become FoundationDB range reads.
Values: a binary codec¶
Item attributes are serialized with a compact, type-tagged, deterministic binary
codec (codec.go) that supports all ten DynamoDB attribute types
(S, N, B, BOOL, NULL, M, L, SS, NS, BS). Items under ~10 KB
are stored as a single FoundationDB value.
Chunked large items¶
FoundationDB values have a size limit, so items larger than ~10 KB are split. The
primary value becomes a chunked:N manifest and the payload is written across
c/.../<chunk_idx> keys. Reads fetch all chunks in parallel using FoundationDB
futures and reassemble them.
flowchart LR
W["PutItem (large)"] --> M["i/<pk> = chunked:N"]
W --> C0["c/<pk>/0"]
W --> C1["c/<pk>/1"]
W --> Cn["c/<pk>/…N-1"]
Secondary indexes¶
Global (GSI) and local (LSI) secondary indexes share one subspace (x/) and one
write path. An index entry's key is
x/<index>/<indexHashValue>/<indexSortValue>/<basePK>, where the base table's
primary key is appended as a tiebreaker so that multiple items with the same index
value never collide.
The key property: index entries are written in the same FoundationDB transaction as the base item. Indexes are not maintained asynchronously, so there is no propagation delay and no window in which an index disagrees with the base table. In DynamoDB, global secondary indexes are eventually consistent.
flowchart TB
P["PutItem / UpdateItem / DeleteItem"]
subgraph txn["single FoundationDB transaction"]
B["base item (i/)"]
G["GSI entries (x/)"]
L["LSI entries (x/)"]
S["change record (v/)"]
end
P --> txn
See Transactions & consistency for what this guarantees and
Change streams for the v/ subspace.