Transactions
Every request you send to ReifyDB runs inside a transaction, automatically. Writes commit atomically with serializable snapshot isolation, reads see a consistent snapshot, and conflicts are retried for you. There is no BEGIN, COMMIT, or ROLLBACK to manage - transaction handling is the engine's job, not yours.
Queries, commands, and admin
ReifyDB splits work into three kinds of transactions, and the clients expose them as three entry points:
- --A query transaction (
query) only reads. It runs under snapshot isolation: it pins the latest committed state when it starts and sees exactly that state for its entire run - never a half-applied write, no matter how many writers are active. - --A command transaction (
command) writes data: inserts, updates, deletes, procedure calls. It runs under serializable snapshot isolation - the engine tracks what each command reads and writes, and only lets it commit if the result is equivalent to the commands running one after another. A command cannot change schema. - --An admin transaction (
admin) is the only kind that can execute DDL -create,alter,drop, migrations, access control. It can also write data, so a schema change and the writes that go with it commit as one atomic unit.
Keeping schema changes out of command transactions means day-to-day application traffic physically cannot alter the schema - evolving it is a deliberate, separately privileged operation. There is no isolation level to configure and no weaker mode to fall back to.
One request, one transaction
Each request is exactly one transaction. A request may contain many statements - they all commit together, or none of them do. This transfer touches two rows in two separate update statements, and no reader can ever observe the state between them (the runnable snippets on this page execute as admin requests, which is why they can mix create statements with writes):
If any statement fails, the whole request rolls back. Here the update executes first and the cast fails afterwards:
The update was rolled back with everything else - the balances are untouched:
rollback keyword you may encounter in migrations is unrelated: it declares compensating statements for reverting a migration, not transaction control.Readers never block writers
ReifyDB uses multi-version concurrency control (MVCC). Every commit produces a new version of the rows it touched instead of overwriting them in place, and every committed transaction is stamped with a monotonically increasing commit version. A query simply reads as of the newest committed version at the moment it starts. It takes no locks, blocks no command, and is blocked by none - long analytical reads and high-frequency writes coexist without queueing on each other.
Conflicts and automatic retries
Commands are optimistic: they do not lock rows up front. Instead, each command works against its own snapshot, and at commit the engine validates that no other transaction has committed a change that overlaps with what this command read or wrote. If validation fails, the command aborts with a conflict error (TXN_001) - and the server retries it automatically with backoff, up to 10 attempts by default. Each retry re-executes the request from scratch against the newest state, so the retried command sees the data that beat it to the commit.
In practice you rarely see conflicts at all: queries never conflict with anything, and commands only conflict when they genuinely race over the same data. If a command still fails with TXN_001 after all retries, the client receives the error and can decide whether to resubmit.
Views, handlers, and transactions
Derived state participates in the same guarantees. Transactional views are maintained inside the commit of the write that affects them - the table and the view change in the same atomic step:
Write to the source table:
The view is already current - there is no window in which the table shows the new order but the view shows the old revenue:
That timing has one consequence worth knowing: once a request writes to a view's source data, reading that view later in the same request is an error (TXN_015), and the whole request fails and rolls back. Mid-request the view still holds its pre-request contents, and ReifyDB fails loudly rather than hand you stale data:
The failed request committed nothing, and reading the view in its own request works as before:
The rule applies to transactional and deferred views alike, and it follows view chains - a view built on top of another view is protected too. Reading a view before writing to its sources is fine. The remedy depends on the view kind: a transactional view is current the moment the write returns, so split the write and the read into separate requests, or read the source tables directly. A deferred view updates asynchronously after commit, so read the source tables directly, or consume the view through a subscription:
Writing upstream of the deferred view and reading it in the same request fails the same way:
Handlers run synchronously inside the writing transaction too - if a handler fails, the write that triggered it rolls back. Deferred views and subscriptions are the asynchronous side: they consume committed changes after the fact, ordered by commit version, and are eventually consistent.
Under the hood
A command buffers its writes locally and records the keys and ranges it reads. At commit, a central coordinator checks that read/write set against every transaction that committed since the command's snapshot; if nothing overlaps, it allocates the next commit version and publishes the write-set - to storage, to transactional view maintenance, and to the change log that feeds subscriptions, all under that one version. For the full mechanics - the commit pipeline, conflict windows, and watermarks - see Transaction Internals.
