Skip to main content
DB
ProductOLTP

Writes that land once and stay exact

Every statement is one atomic write. It is in the write-ahead log before you hold the receipt, the next read sees it, and the row keeps the version your producers meant, whatever order their writes arrive in.

The write path

Three ways to write a row, one receipt each

Create a row, change part of one, or write a complete one. Each statement carries one row, is validated whole and commits whole, and you hold its receipt before anything else sees it.

  1. 01

    Add a fact

    An event, a reading or a transfer joins an append table as a new row. On a keyed table the same statement reports a key that already exists instead of writing it twice.

    INSERT · one row per statement

    a new row in an append table
    INSERT INTO transfers (
    chain_id, token, sender, recipient, amount, transferred_at, _index
    )
    VALUES (
    1, 'USDC', '0x4b1e7c2a', '0x9c2e4f1a', 2500000000, '2026-10-01 12:00:00.000',
    1001
    );
  2. 02

    Change the columns you name

    Name the key and the columns that moved. The other columns keep their values, and a key that does not exist yet gets its row.

    UPSERT · the named columns of one key

    the named columns of one key
    UPSERT INTO balances (
    chain_id, address, token, amount, updated_at, balance_index, balance_version
    )
    VALUES (
    1, '0x9c2e4f1a', 'USDC', 2500000000, '2026-10-01 12:00:00.000', 1001, 1
    );
  3. 03

    Write the whole row

    Every column at once, superseding whatever was written before for that key, attribute groups included.

    REPLACE · a complete row for one key

    a complete row for one key
    REPLACE INTO balances (
    chain_id, address, token, amount, updated_at, balance_index, balance_version,
    transfer_count, last_transfer_at, activity_index
    )
    VALUES (
    1, '0x9c2e4f1a', 'USDC', 0, '2026-10-02 00:00:00.000', 2001, 1, 0, NULL, 2001
    );
  4. VALUES (…), (…) writes several rows in one statement. Bulk routes carry volume; see below.

    INSERT, UPSERT and REPLACE

Consistency

Durable before the receipt, visible to the next read

Three guarantees your application builds on, in the order a write meets them. The same contract holds on every table and every request.

  1. Receipt01

    Acknowledged only once it is durable

    The tablet’s owner orders your write and assigns it a commit sequence. Its records join a group, the group reaches the write-ahead log and passes one persistence barrier, and only then do you get the receipt and does anything derived from it hear about it.

    commit sequence → WAL group → barrier → receipt

  2. Read-after-write02

    The next read sees your write, no retry

    When the receipt returns, the read that follows sees the write. A read that overlaps a write returns the state before it or the state after it, never a partly applied row.

    receipt → next read

  3. Atomic03

    One statement, or one transaction, commits whole

    A statement is validated completely before it commits and is applied completely or not at all. Open a transaction to group writes across rows, groups and tables, and they become visible together at the commit.

    BEGIN … COMMIT · ROLLBACK

Table modes

Decide what each table remembers

A table is a typed schema plus a mode. The mode decides which writes the table accepts and which state it keeps; the key, the shards and the layout are declared beside it and never change the mode.

The three table modes, with the key each takes, the writes it accepts and the state it keeps.
APPENDnone: the engine assigns each row its identityINSERTevery row
UPSERT (the default)exactly one PRIMARY KEYINSERT, UPSERT, REPLACE, UPDATE, DELETEthe latest row per key
VERSIONEDexactly one PRIMARY KEYthe same as UPSERTthe current row per key and every accepted version

VALID BY (…) and REVISION BY (…) decide which version is current when writes arrive out of order; commit order breaks any remaining tie.

Tables in the docs

Attribute groups

Several writers, one row, nothing overwritten

Split a keyed row into named column groups and give each producer its own. A write to one group changes only that group’s columns, keeps its own version order, and never holds back a newer write to another. A read still returns the whole row.

own write domain
A stale write to one group is kept as an earlier version of that group; the other groups are untouched.
one group at a time
The first write on a new key stores the key and that group. Columns no one has written yet read as NULL.
INTO … ATTRIBUTE GROUP
A transformation can own one group of the table and leave the rest of the row to other producers.
one row, two writers, one table
CREATE TABLE balances (
chain_id UINT16 NOT NULL,
address STRING NOT NULL,
token STRING NOT NULL,
amount UINT256 NULL,
updated_at DATETIME64(3, 'UTC') NULL,
balance_index UINT128 NULL,
balance_version UINT64 NULL,
transfer_count UINT64 NULL,
last_transfer_at DATETIME64(3, 'UTC') NULL,
activity_index UINT128 NULL,
PRIMARY KEY (chain_id, address, token),
ATTRIBUTE GROUP holding (amount, updated_at, balance_index, balance_version)
VALID BY (balance_index)
REVISION BY (balance_version),
ATTRIBUTE GROUP activity (transfer_count, last_transfer_at, activity_index)
VALID BY (activity_index)
)
MODE VERSIONED
SHARD BY (address) SHARDS AUTO
ORDER BY (chain_id, address, token);

Point-in-time reads

Read the row as it stood at any commit

A versioned table keeps every accepted version beside the current row. Read it at an earlier commit and you get each key’s winner at that commit, under the schema you have now: the state behind a decision, an audit or a replay.

Which version wins is decided by the columns you declare, never by arrival order. Writes delivered in order, shuffled or late converge to the same current row and the same retained history.

balances at an earlier commit
SELECT address, token, amount
FROM balances AS OF COMMIT 42
WHERE chain_id = 1;
Four versions of one key, in arrival order
Four writes to one key arriving in order, with the current row after each.
11001write 1
22001write 2 (later index)
31509still write 2: 150 is earlier than 200, so the high revision does not matter
42002write 4 (same index as write 2, higher revision)

Bulk ingest

Load in bulk through the route that fits

One row per statement is the SQL form. For volume, push bounded batches or native frames over HTTP, or the same frames over the WebSocket session you already hold. Every table and every request meets the same durability contract.

  1. POST /write/batch01

    A bounded batch of rows to one table

    Send a bounded set of rows for one table in one request, JSON in and out, under the same session you use for statements. The TypeScript client sends these as bounded batch writes.

    HTTP · JSON · one table per request

  2. POST /bulk-load/frame02

    Native columnar frames for volume

    One native columnar bulk-load frame per request. The TypeScript client drives resumable bulk loads over it, so a long load picks up where it stopped.

    HTTP · native frame · resumable from the client

  3. POST /insert/logical-block03

    Logical insert blocks, over HTTP or your socket

    One logical block of inserts per request, with its own content type. The WebSocket session carries the same native bulk and logical insert frames beside your queries.

    Content-Type: application/vnd.nyxdb.logical-insert

Frequently asked questions

The questions a team asks before it points its write path at a new database. Anything else is in the docs.

Yes, on every table and every request. The write’s records join a group in the write-ahead log and pass a persistence barrier, and only then does the receipt return and does anything derived from the write hear about it.

Write it once, read it exact

Create a keyed table, write one group of a row, and read it back at the commit you choose. The first-queries walkthrough takes you there.