Storage model
PizzaSQL stores everything in PizzaKV, a key-value store, as JSON documents under a namespaced key scheme. There are no B-trees, no heap files, and no page format — just keys and values.
Namespacing
Section titled “Namespacing”Every key is prefixed by the database name, so each database is an isolated namespace within the same PizzaKV store. Table and index names are lowercased in keys, making them case-insensitive at the storage layer.
Key layout
Section titled “Key layout”| Key pattern | Value |
|---|---|
<db>:_sys:tables |
JSON array of table names (the catalog) |
<db>:_schema:<table> |
JSON table schema (columns, primary key, NextRowID, AutoIncrement) |
<db>:_sys:rowid:<table> |
rowid counter state |
<db>:_data:<table>:<pk> |
a single row, as a JSON object |
<db>:indexes |
JSON array of index names |
<db>:index:<name> |
JSON index definition (name, table, columns, unique) |
<db>:idx:<name>:<value> |
index entry (value → rowids); only ever cleared, never written |
The table schema is the source of truth for a table’s columns and its NextRowID counter. Rows are keyed by their primary-key value; the rowid (_rowid_) is stored inside the JSON row itself.
Rows are JSON
Section titled “Rows are JSON”A row is map[string]interface{} serialized as JSON. Consequences:
- Numbers round-trip through JSON as floats, so integer columns are normalized back to
int64after reading (see Data types). NULLis JSONnull.- BLOBs are strings.
The storage layer reads a whole table by issuing a reads <db>:_data:<table>: prefix scan and deserializing each value.
The in-memory layer
Section titled “The in-memory layer”On top of PizzaKV, each TableManager/SchemaManager keeps caches:
- Row cache: a table’s rows, loaded on first
SELECTand invalidated on write. - Rowid map: rowid → row, for index lookups.
- Index entry cache: value → rowids for each warm index (see Indexes).
- Schema cache: table and index definitions.
These caches are per database instance and are what make warm reads fast. They are derived state: after a restart they are rebuilt lazily from the durable keys.
Rowid management
Section titled “Rowid management”The next rowid is tracked in the table schema’s NextRowID field. On first use after startup, the engine scans the table’s rows to recover max(rowid) + 1 (so rowid state doesn’t need its own write-ahead entry). INTEGER PRIMARY KEY aliases this rowid; other primary keys get a separate, invisible rowid counter.
What is and isn’t durable
Section titled “What is and isn’t durable”- Durable: table schemas, the catalog list, index definitions, and row data.
- Not durable (derived/rebuilt): rowid counter (recovered from data), index entries, and all in-memory caches.
This split is why index definitions survive a restart but their entries must be rebuilt, and why the first query after a cold start is slower than subsequent ones.
Transactions and storage
Section titled “Transactions and storage”Transactions are handled at the SQL layer (an undo log), not by the key-value store. Each statement’s writes go to PizzaKV immediately; ROLLBACK replays the undo log to restore prior values. There is no multi-key atomicity at the PizzaKV level — the undo log is what provides it for row writes (DDL is not covered; see Transactions).
The managed service’s storage
Section titled “The managed service’s storage”On database.pizza, each database is a PizzaSQL namespace inside a managed PizzaKV instance, provisioned and routed per organization. From your perspective the storage is opaque; the guarantees above still apply to your data, including the durability of schema/index definitions and row data.