# Concurrency, isolation and durability

The transaction isolation levels ParticleDB supports, the concurrency-control modes and what each level does under them, and the durability modes.

Source: https://particledb.ai/operations/modes/

ParticleDB has three independent settings: transaction isolation (what a transaction reads), concurrency control (how conflicts between concurrent transactions are detected) and durability (when a commit is on disk). Each is configured separately; changing one does not change the others.

## Transaction isolation levels

Default: READ COMMITTED (`--default-isolation read-committed`), as in PostgreSQL.

| Level | What the application observes |
|---|---|
| READ COMMITTED (default) | Each statement sees data committed before that statement began |
| REPEATABLE READ | Snapshot isolation: the whole transaction reads one snapshot, and write conflicts are refused at commit |
| SERIALIZABLE | Read and predicate dependencies are validated, so the result matches some serial order |

Change the server default with `--default-isolation read-committed|repeatable-read|serializable`. A session or transaction chooses its own level:

```sql
BEGIN ISOLATION LEVEL SERIALIZABLE;
SHOW transaction_isolation;
COMMIT;

-- change the default for later transactions on this connection
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;
```

Keep all of a transaction's statements on one connection. Choose the level at `BEGIN` (or through the client's transaction API), or with `SET TRANSACTION` before its first query. Changing the session default does not upgrade a transaction that is already running.

## Concurrency control: `--concurrency-control`

Default: `optimistic`. This server-wide setting selects how conflicts between concurrent transactions are detected.

| Name | What it does | Default isolation level |
|---|---|---|
| `optimistic` (default) | Checks for conflicts when a transaction commits, according to its isolation level | READ COMMITTED |
| `strict-optimistic` | Everything `optimistic` does, with stricter reads, recovery and autocommit handling, so SERIALIZABLE also guarantees real-time order | READ COMMITTED |
| `none` | No conflict checks. Concurrent writers can lose each other's updates. For bulk loads and single-writer ingest only | READ COMMITTED |

```bash
particledb start --concurrency-control optimistic --data-dir ./pdb-data
```

## What to expect in each concurrency-control mode

### `optimistic` (default)

Conflict validation at commit. Recommended for general application workloads.

| Isolation level | What to expect |
|---|---|
| READ COMMITTED (default) | Each statement sees data committed before it began. Some conflicting updates are re-applied to the latest committed row by the server; any other conflict fails the commit with SQLSTATE `40001` |
| REPEATABLE READ | Snapshot isolation. A write conflict fails the commit with `40001` |
| SERIALIZABLE | Serializable: a commit that no serial order could produce fails with `40001`. Real-time order held in every test but is not guaranteed by construction, because reads may fall back to an older row copy |

When the server returns `40001` (`serialization_failure`), roll back and retry the complete transaction with a bounded policy, and keep external actions idempotent. Internal re-application does not remove the need for retry handling.

### `strict-optimistic`

`optimistic` validation plus authoritative committed reads, strict recovery and durable-before-visible autocommit writes. Required for strict serializability.

| Isolation level | What to expect |
|---|---|
| READ COMMITTED (default) | Same as `optimistic`. An explicit READ COMMITTED transaction is never upgraded |
| REPEATABLE READ | Snapshot isolation, as in `optimistic` |
| SERIALIZABLE | Strict serializable: serializable plus real-time ordering, guaranteed by construction |

Also server-wide: reads never fall back to an older row copy, startup refuses an incomplete recovery, autocommit writes become visible only once they are durable (when WAL logging is on), and unlabelled autocommit `SKIP LOCKED` claims and predicate `UPDATE`s run strict serializable.

### `none`

No conflict validation. Bulk loads and single-writer ingest of reproducible data only.

| Isolation level | What to expect |
|---|---|
| READ COMMITTED (default) | Accepted. No isolation guarantee: without conflict validation, concurrent writers can lose updates without an error |
| REPEATABLE READ | Refused with SQLSTATE `0A000` (`feature_not_supported`) |
| SERIALIZABLE | Refused with SQLSTATE `0A000` (`feature_not_supported`) |

The refusal applies in every form, including `--default-isolation` at startup. Do not use it with concurrent writers. It does not disable the write-ahead log; durability is configured separately.

### Statements outside an explicit transaction

An explicit `BEGIN` with no isolation clause uses the session or server default. Unlabelled autocommit statements can follow the server's concurrency-control setting, as described for `strict-optimistic`; an explicitly selected session isolation level takes precedence. For business rules spanning several statements, use an explicit transaction with an explicit level.

## Durability: `--durability`

Default: `groupsync`.

| Name | How it works | What to expect after a crash |
|---|---|---|
| `groupsync` (default) | COMMIT returns after the commit's WAL record is flushed to disk. Commits that arrive together share one flush | Every acknowledged commit survives |
| `sync` | COMMIT returns after the commit's own flush to disk | Every acknowledged commit survives |
| `none` | COMMIT returns immediately. The write-ahead log is off | Nothing: acknowledged commits are lost on any restart, even a clean shutdown, and there is no crash recovery |

`groupsync` and `sync` give the same guarantee; `groupsync` is faster under concurrency because one flush covers many commits. `none` is refused with replication; use it only for data you can recreate.

On macOS a flush may not empty the drive's cache, so a power loss can still lose recently acknowledged commits there. Use a durable setting for application records and test recovery on your actual platform and storage. See [durability and restart](/operations/durability/).

## KV durability: `--kv-durability`

Default: `async`. This applies to KV commands on the [Redis-compatible endpoint](/connectivity/redis/#durability); KV writes over the native PDB wire always follow `--durability`.

| Name | How it works | What can lose an acknowledged write |
|---|---|---|
| `async` (default) | A KV write is acknowledged once its WAL bytes reach the operating system | An OS crash or power loss can lose recent acknowledgements. A process crash loses nothing acknowledged |
| `sync` | A KV write is acknowledged after the flush to disk | Only failures outside the `--durability` guarantee |

The default falls back to `sync` (with a log line) under `--durability none`, a striped WAL (`--num-wal-stripes` above 1) or replication. An explicit `--kv-durability async` in those configurations is refused. At runtime, `CONFIG SET pdb-kv-durability sync|async` on the Redis-compatible endpoint changes the setting until the next restart.

## Experimental modes (not in current release builds)

The modes below were experimental. They appeared only in pre-release builds before `1.1.19-rc.13`, under that build's `--txn-mode` flag, including the builds examined in the Jepsen analysis of ParticleDB, and were never a supported configuration. None of them is in current release builds. Independent testing of those builds found safety errors in them. Each name now fails at startup with `Error: invalid --concurrency-control ''`, exit code 1, and the list of accepted values.

| Removed name | What it was | Use instead |
|---|---|---|
| `occ-v3`, `occv3`, `occ-v2`, `occv2`, `serializable-occ` | Earlier names of the optimistic engine | `optimistic` |
| `zero-copy-occ`, `zcocc` | Experimental partition-pinned variant of the optimistic engine | `optimistic` |
| `table-2pl`, `2pl`, `row-2pl`, `row_2pl` | Experimental table-lock engine; by the time it was removed it no longer used two-phase locking | `strict-optimistic` |
| `serializable-ssi`, `ssi` | Experimental serializable snapshot isolation engine | `strict-optimistic` |
| `strict-serializable`, `strict-ser` | Earlier name of strict serializable mode | `strict-optimistic` |

In some pre-release builds, `serializable` was itself an alias of `table-2pl`; the current `strict-optimistic` is a different, newer mode. Removing a name does not change findings about the builds that accepted it. If you ran a pre-release build, upgrade, choose one of the current modes above, and check your data against a trusted source. See [release history](/installation/releases/#release-history).

## Related

[Transactions](/sql/transactions/) · [Durability and restart](/operations/durability/) · [Coordinate concurrent updates](/sql/locking/) · [CLI reference](/operations/cli-reference/)
