From fb8e36a6502c6d1c0e01e311d3e39de069caa791 Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Mon, 3 Aug 2026 14:04:48 -0400 Subject: [PATCH 1/2] feat(txn)!: DB_TXN_SNAPSHOT is now serializable snapshot isolation (SSI) BREAKING (ABI): DB_TXN_SNAPSHOT now provides serializable snapshot isolation (SSI) -- snapshot isolation plus Cahill serializable conflict detection -- and the separate DB_TXN_SNAPSHOT_SAFE flag (0x800) is REMOVED. There is no longer a plain, non-serializable SI mode via the public API: every snapshot transaction gets SSI and may be aborted with DB_SNAPSHOT_UNSAFE / DB_SNAPSHOT_CONFLICT, and cannot be prepared for two-phase commit. This drops the awkward _SAFE flag name in favor of the natural DB_TXN_SNAPSHOT, at the cost of a deliberate ABI break (the maintainer accepted this). - txn.c: DB_TXN_SNAPSHOT (and the env-wide DB_ENV_TXN_SNAPSHOT) now set both the internal TXN_SNAPSHOT substrate and TXN_SNAPSHOT_SAFE (SSI) states; removed DB_TXN_SNAPSHOT_SAFE from the txn_begin OK_FLAGS mask; prepare() rejection message updated to name DB_TXN_SNAPSHOT. - dist/api_flags + regenerated src/dbinc_auto/api_flags.in + build_windows/db.h: DB_TXN_SNAPSHOT_SAFE definition removed; its 0x800 bit is freed. - db.in: internal TXN_SNAPSHOT comment clarified (substrate, always with SSI). - lang/tcl: -snapshot_safe kept as a compat alias for -snapshot (both now SSI) so existing SSI tests keep working unchanged. The internal TXN_SNAPSHOT_SAFE state and all SSI machinery (SIREAD markers, the lock.c GC, db_meta.c conflict recording) are unchanged. Verified: builds clean; ssi001-009, txn001/003, test001 btree/hash all pass. --- build_windows/db.h | 3 +-- dist/api_flags | 3 +-- lang/tcl/tcl_txn.c | 8 +++++++- src/dbinc/db.in | 2 +- src/dbinc_auto/api_flags.in | 1 - src/txn/txn.c | 15 ++++++++++----- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/build_windows/db.h b/build_windows/db.h index ddf07507f..0e05f0a16 100644 --- a/build_windows/db.h +++ b/build_windows/db.h @@ -985,7 +985,7 @@ struct __db_txn { #define TXN_READ_COMMITTED 0x01000 /* Txn has degree 2 isolation. */ #define TXN_READ_UNCOMMITTED 0x02000 /* Txn has degree 1 isolation. */ #define TXN_RESTORED 0x04000 /* Txn has been restored. */ -#define TXN_SNAPSHOT 0x08000 /* Snapshot Isolation. */ +#define TXN_SNAPSHOT 0x08000 /* Snapshot isolation substrate (always with SSI). */ #define TXN_SYNC 0x10000 /* Write and sync on prepare/commit. */ #define TXN_WRITE_NOSYNC 0x20000 /* Write only on prepare/commit. */ #define TXN_BULK 0x40000 /* Enable bulk loading optimization. */ @@ -3050,7 +3050,6 @@ typedef struct entry { #define DB_TXN_NOT_DURABLE 0x00000004 #define DB_TXN_NOWAIT 0x00000002 #define DB_TXN_SNAPSHOT 0x00000004 -#define DB_TXN_SNAPSHOT_SAFE 0x00000800 #define DB_TXN_SYNC 0x00000008 #define DB_TXN_WAIT 0x00000080 #define DB_TXN_WRITE_NOSYNC 0x00000020 diff --git a/dist/api_flags b/dist/api_flags index d2c8bbfb7..9febf8f5c 100644 --- a/dist/api_flags +++ b/dist/api_flags @@ -275,8 +275,7 @@ DbEnv.txn_begin DB_TXN_NOWAIT # Do not wait for locks DB_TXN_FAMILY # Cursors and child txns are # independent but lock-compatible - DB_TXN_SNAPSHOT # Snapshot isolation - DB_TXN_SNAPSHOT_SAFE __PIN=0x00000800 # Serializable snapshot isolation (SSI) + DB_TXN_SNAPSHOT # Serializable snapshot isolation (SSI) DB_TXN_SYNC # Always sync log on commit DB_TXN_WAIT # Always wait for locks in this txn DB_TXN_WRITE_NOSYNC # Write the log but don't sync diff --git a/lang/tcl/tcl_txn.c b/lang/tcl/tcl_txn.c index 258a12b72..1071e9cd7 100644 --- a/lang/tcl/tcl_txn.c +++ b/lang/tcl/tcl_txn.c @@ -265,7 +265,13 @@ get_timeout: if (i >= objc) { flag |= DB_TXN_SNAPSHOT; break; case TXNSNAPSHOTSAFE: - flag |= DB_TXN_SNAPSHOT_SAFE; + /* + * DB_TXN_SNAPSHOT is now serializable snapshot isolation + * (SSI); the separate DB_TXN_SNAPSHOT_SAFE flag was + * removed. Keep -snapshot_safe as a compatibility alias + * for -snapshot so existing SSI tests keep working. + */ + flag |= DB_TXN_SNAPSHOT; break; case TXNSYNC: flag |= DB_TXN_SYNC; diff --git a/src/dbinc/db.in b/src/dbinc/db.in index e7a00974f..84c58d625 100644 --- a/src/dbinc/db.in +++ b/src/dbinc/db.in @@ -954,7 +954,7 @@ struct __db_txn { #define TXN_READ_COMMITTED 0x01000 /* Txn has degree 2 isolation. */ #define TXN_READ_UNCOMMITTED 0x02000 /* Txn has degree 1 isolation. */ #define TXN_RESTORED 0x04000 /* Txn has been restored. */ -#define TXN_SNAPSHOT 0x08000 /* Snapshot Isolation. */ +#define TXN_SNAPSHOT 0x08000 /* Snapshot isolation substrate (always with SSI). */ #define TXN_SYNC 0x10000 /* Write and sync on prepare/commit. */ #define TXN_WRITE_NOSYNC 0x20000 /* Write only on prepare/commit. */ #define TXN_BULK 0x40000 /* Enable bulk loading optimization. */ diff --git a/src/dbinc_auto/api_flags.in b/src/dbinc_auto/api_flags.in index 4546fe0d1..9fd49b03b 100644 --- a/src/dbinc_auto/api_flags.in +++ b/src/dbinc_auto/api_flags.in @@ -196,7 +196,6 @@ #define DB_TXN_NOT_DURABLE 0x00000004 #define DB_TXN_NOWAIT 0x00000002 #define DB_TXN_SNAPSHOT 0x00000004 -#define DB_TXN_SNAPSHOT_SAFE 0x00000800 #define DB_TXN_SYNC 0x00000008 #define DB_TXN_WAIT 0x00000080 #define DB_TXN_WRITE_NOSYNC 0x00000020 diff --git a/src/txn/txn.c b/src/txn/txn.c index 32879909d..29b29e57a 100644 --- a/src/txn/txn.c +++ b/src/txn/txn.c @@ -112,7 +112,6 @@ __txn_begin_pp(dbenv, parent, txnpp, flags) DB_IGNORE_LEASE |DB_READ_COMMITTED | DB_READ_UNCOMMITTED | DB_TXN_FAMILY | DB_TXN_NOSYNC | DB_TXN_SNAPSHOT | DB_TXN_SYNC | DB_TXN_WAIT | DB_TXN_WRITE_NOSYNC | DB_TXN_NOWAIT | - DB_TXN_SNAPSHOT_SAFE | DB_TXN_BULK)) != 0) return (ret); if ((ret = __db_fcchk(env, "txn_begin", flags, @@ -231,7 +230,7 @@ __txn_begin(env, ip, parent, txnpp, flags) F_SET(txn, TXN_READ_UNCOMMITTED); if (LF_ISSET(DB_TXN_FAMILY)) F_SET(txn, TXN_FAMILY | TXN_INFAMILY | TXN_READONLY); - if (LF_ISSET(DB_TXN_SNAPSHOT | DB_TXN_SNAPSHOT_SAFE) || + if (LF_ISSET(DB_TXN_SNAPSHOT) || F_ISSET(dbenv, DB_ENV_TXN_SNAPSHOT) || (parent != NULL && F_ISSET(parent, TXN_SNAPSHOT))) { if (IS_REP_CLIENT(env)) { @@ -241,8 +240,14 @@ __txn_begin(env, ip, parent, txnpp, flags) } else F_SET(txn, TXN_SNAPSHOT); } - /* SSI is snapshot isolation plus serializable conflict detection. */ - if (LF_ISSET(DB_TXN_SNAPSHOT_SAFE) || + /* + * DB_TXN_SNAPSHOT is serializable snapshot isolation (SSI): snapshot + * isolation plus serializable conflict detection. Any snapshot + * transaction gets SSI -- there is no separate plain-SI mode. (A + * replication client already returned EINVAL above, so we only reach + * here for a snapshot txn that is allowed to be SSI.) + */ + if (F_ISSET(txn, TXN_SNAPSHOT) || (parent != NULL && F_ISSET(parent, TXN_SNAPSHOT_SAFE))) { F_SET(txn, TXN_SNAPSHOT_SAFE); /* @@ -1378,7 +1383,7 @@ __txn_prepare(txn, gid) */ if (F_ISSET(txn, TXN_SNAPSHOT_SAFE)) { __db_errx(env, DB_STR("4575", - "DB_TXN->prepare: DB_TXN_SNAPSHOT_SAFE (SSI) transactions " + "DB_TXN->prepare: DB_TXN_SNAPSHOT (SSI) transactions " "cannot be prepared for two-phase commit")); ret = EINVAL; goto err; From bb842019ac61e78ac3aa4171662365da8c39dd62 Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Mon, 3 Aug 2026 14:23:40 -0400 Subject: [PATCH 2/2] docs,test: reflect DB_TXN_SNAPSHOT-is-SSI across docs, RFC, bench Follow the DB_TXN_SNAPSHOT_SAFE removal through the rest of the tree: - docs_src: txnbegin.md merges the two flag entries into one DB_TXN_SNAPSHOT (SSI) description; envset_flags.md, the isolation + transaction-read guides, and program_errorret.md updated; a compatibility note explains the fork's change vs stock Oracle BDB. flag-reconcile.md records the removal. - README.md: the SSI bullet now says DB_TXN_SNAPSHOT is serializable + notes the ABI break. - rfc/0003 (SSI): an Amendment note records the API simplification; INDEX updated. - test/bench/ssi_abort_bench.c: uses DB_TXN_SNAPSHOT (the removed _SAFE flag would not compile); builds clean. TCL -snapshot_safe stays a compat alias (prior commit); the .tcl SSI tests are unchanged and pass. Docs build clean. --- README.md | 9 ++++++--- docs_src/_migrate/flag-reconcile.md | 20 ++++++++++--------- docs_src/api/c/envset_flags.md | 2 +- docs_src/api/c/txnbegin.md | 12 ++++------- docs_src/guides/gsg_txn/isolation.md | 6 +++--- .../programmer_reference/program_errorret.md | 4 ++-- .../programmer_reference/transapp_read.md | 6 +++--- ...003-ssi-serializable-snapshot-isolation.md | 16 ++++++++++++--- rfc/INDEX.md | 2 +- test/bench/ssi_abort_bench.c | 4 ++-- 10 files changed, 46 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 5930c1230..aae18697e 100644 --- a/README.md +++ b/README.md @@ -45,15 +45,18 @@ for full provenance and the per-version index. ## What's new on the living fork -- **Serializable Snapshot Isolation (SSI)** — an opt-in `DB_TXN_SNAPSHOT_SAFE` - transaction mode that detects dangerous read/write dependency structures and +- **Serializable Snapshot Isolation (SSI)** — the `DB_TXN_SNAPSHOT` + transaction mode is now serializable: it detects dangerous read/write + dependency structures and aborts the pivot with `DB_SNAPSHOT_CONFLICT`. Both of Cahill's rw-conflict detection mechanisms are implemented: the lock-table path (a concurrent writer meeting a reader's SIREAD marker) and the MVCC version-chain path in `mp_fget` (a reader handed an older version than one a concurrent writer committed). SIREAD markers are reclaimed incrementally and bounded (not only at checkpoint); the commit-time pivot check is race-free against concurrent - edge recording; and `DB_TXN_SNAPSHOT_SAFE` is rejected with `prepare()`/2PC. + edge recording; and a `DB_TXN_SNAPSHOT` transaction is rejected with `prepare()`/2PC. + (This fork makes `DB_TXN_SNAPSHOT` serializable and removed the earlier + separate `DB_TXN_SNAPSHOT_SAFE` flag — a deliberate ABI break.) The SIREAD marker/locker/detail lifetime is hardened for concurrent writers: a family of pre-existing use-after-free bugs (most importantly a lock object reclaimed while it still held SIREAD markers) was found with TSan/ASan and diff --git a/docs_src/_migrate/flag-reconcile.md b/docs_src/_migrate/flag-reconcile.md index ae444b02e..79d771c7c 100644 --- a/docs_src/_migrate/flag-reconcile.md +++ b/docs_src/_migrate/flag-reconcile.md @@ -24,8 +24,8 @@ environment / database flag surfaces the SSI work touched. Sources of truth: | DB_TXN_FAMILY | **MISSING** | **added** (txnbegin.md) | | DB_TXN_NOSYNC | doc | doc | | DB_TXN_NOWAIT | doc | doc | -| DB_TXN_SNAPSHOT | doc | doc | -| DB_TXN_SNAPSHOT_SAFE | **MISSING** | **added** (txnbegin.md, +guides, +return codes) | +| DB_TXN_SNAPSHOT | doc | doc (now = SSI) | +| DB_TXN_SNAPSHOT_SAFE | *removed* | flag removed — folded into DB_TXN_SNAPSHOT | | DB_TXN_SYNC | doc | doc | | DB_TXN_WAIT | doc | doc | | DB_TXN_WRITE_NOSYNC | doc | doc | @@ -58,10 +58,11 @@ Every `txn_begin` flag the engine accepts is now documented. | DB_YIELDCPU | doc | | DB_HOTBACKUP_IN_PROGRESS | doc | -Note: `DB_TXN_SNAPSHOT_SAFE` is deliberately **not** a valid `set_flags` -flag — SSI is a per-transaction property only (the env `OK_FLAGS` mask accepts -plain `DB_TXN_SNAPSHOT` but not `_SAFE`). `envset_flags.md` now states this -explicitly under `DB_TXN_SNAPSHOT`. +Note: `DB_TXN_SNAPSHOT` is now serializable snapshot isolation (SSI); the +separate `DB_TXN_SNAPSHOT_SAFE` flag was **removed** and its behavior folded +into `DB_TXN_SNAPSHOT`, which is a valid `set_flags` env flag (SSI can be set +environment-wide or per-transaction). `envset_flags.md` states this under +`DB_TXN_SNAPSHOT`. ## Public return codes (src/dbinc/db.in) — SSI-era @@ -75,9 +76,10 @@ return *code* an SSI transaction may get, not a `txn_begin` option. ## What this change fixed -- `DB_TXN_SNAPSHOT_SAFE` (0x800) — the SSI flag our engine work added: - documented on `api/c/txnbegin.md`, cross-noted on `api/c/envset_flags.md` - (per-transaction only), and explained in the transactions guide +- `DB_TXN_SNAPSHOT` — now serializable snapshot isolation (SSI); the earlier + separate `DB_TXN_SNAPSHOT_SAFE` (0x800) flag was removed and folded in: + documented on `api/c/txnbegin.md`, cross-noted on `api/c/envset_flags.md`, + and explained in the transactions guide (`guides/gsg_txn/isolation.md`) and the programmer's reference (`guides/programmer_reference/transapp_read.md`). - `DB_SNAPSHOT_UNSAFE` / `DB_SNAPSHOT_CONFLICT` return codes documented in diff --git a/docs_src/api/c/envset_flags.md b/docs_src/api/c/envset_flags.md index 90c71695b..9f72b0779 100644 --- a/docs_src/api/c/envset_flags.md +++ b/docs_src/api/c/envset_flags.md @@ -162,7 +162,7 @@ The **flags** parameter must be set by bitwise inclusively **OR**'ing together o The DB_TXN_SNAPSHOT flag may be used to configure Berkeley DB at any time during the life of the application. - Note that only plain snapshot isolation can be configured environment-wide. The serializable-snapshot-isolation flag DB_TXN_SNAPSHOT_SAFE is a per-transaction flag only: it is accepted by DB_ENV->txn_begin() but not by `DB_ENV->set_flags()`, so SSI must be requested for each transaction individually. + Note that snapshot transactions configured environment-wide with this flag receive serializable snapshot isolation (SSI); `DB_TXN_SNAPSHOT` is now the SSI mode (there is no separate plain-SI flag). The same behavior can be requested per-transaction via DB_ENV->txn_begin(). - `DB_TXN_WRITE_NOSYNC` diff --git a/docs_src/api/c/txnbegin.md b/docs_src/api/c/txnbegin.md index 1fc7d21b5..a6fc161f6 100644 --- a/docs_src/api/c/txnbegin.md +++ b/docs_src/api/c/txnbegin.md @@ -75,17 +75,13 @@ The **flags** parameter must be set to 0 or by bitwise inclusively **OR**'ing to - `DB_TXN_SNAPSHOT` - This transaction will execute with snapshot isolation. For databases with the DB_MULTIVERSION flag set, data values will be read as they are when the transaction begins, without taking read locks. Silently ignored for operations on databases with DB_MULTIVERSION not set on the underlying database (read locks are acquired). + This transaction will execute with **serializable snapshot isolation** (SSI): snapshot isolation plus serializable conflict detection (the Cahill SSI algorithm). For databases with the DB_MULTIVERSION flag set, data values are read as they are when the transaction begins, without taking read locks; for databases without DB_MULTIVERSION, read locks are acquired. On top of that consistent snapshot, Berkeley DB tracks read/write anti-dependencies between concurrent snapshot transactions and, when it detects a dependency structure that could produce a non-serializable schedule, aborts one of the transactions so the committed history is equivalent to some serial order. - The error `DB_LOCK_DEADLOCK` will be returned from update operations if a snapshot transaction attempts to update data which was modified after the snapshot transaction read it. + A transaction that hits such a potential anomaly is aborted with one of two Berkeley DB-specific error returns instead of committing: DB_SNAPSHOT_UNSAFE (a potential serializable-snapshot anomaly was detected via a read/write anti-dependency) or DB_SNAPSHOT_CONFLICT (a conflicting snapshot update was detected). On either return the application must abort the transaction and may retry it. The error `DB_LOCK_DEADLOCK` may also be returned from update operations if a snapshot transaction attempts to update data modified after it read the snapshot. -- `DB_TXN_SNAPSHOT_SAFE` + A `DB_TXN_SNAPSHOT` (SSI) transaction **cannot be prepared for two-phase commit**: DB_TXN->prepare() returns `EINVAL` for such a transaction, because SSI's conflict status is not frozen at prepare time and a later-detected anomaly could not be honored after the transaction had entered the prepared state. - This transaction will execute with **serializable snapshot isolation** (SSI). This is a Berkeley DB extension beyond `DB_TXN_SNAPSHOT`: it implies `DB_TXN_SNAPSHOT` (the transaction reads a consistent snapshot as of its start, as above) and additionally enables serializable conflict detection (the Cahill SSI algorithm). Berkeley DB tracks read/write anti-dependencies between concurrent snapshot-safe transactions and, when it detects a dependency cycle that could produce a non-serializable schedule, aborts one of the transactions so the committed history is equivalent to some serial order. - - A transaction that hits such a potential anomaly is aborted with one of two Berkeley DB-specific error returns instead of committing: DB_SNAPSHOT_UNSAFE (a potential serializable-snapshot anomaly was detected via a read/write anti-dependency) or DB_SNAPSHOT_CONFLICT (a conflicting snapshot update was detected). On either return the application must abort the transaction and may retry it. As with `DB_TXN_SNAPSHOT`, snapshot behavior applies to databases opened with DB_MULTIVERSION. - - A `DB_TXN_SNAPSHOT_SAFE` (SSI) transaction **cannot be prepared for two-phase commit**: DB_TXN->prepare() returns `EINVAL` for such a transaction, because SSI's conflict status is not frozen at prepare time and a later-detected anomaly could not be honored after the transaction had entered the prepared state. + > **Note:** In this fork, `DB_TXN_SNAPSHOT` provides *serializable* snapshot isolation. In stock Oracle Berkeley DB, `DB_TXN_SNAPSHOT` provided only plain (non-serializable) snapshot isolation, and the earlier `DB_TXN_SNAPSHOT_SAFE` flag has been removed — there is no separate non-serializable snapshot mode. - `DB_TXN_SYNC` diff --git a/docs_src/guides/gsg_txn/isolation.md b/docs_src/guides/gsg_txn/isolation.md index 123816139..ce289a5c8 100644 --- a/docs_src/guides/gsg_txn/isolation.md +++ b/docs_src/guides/gsg_txn/isolation.md @@ -396,9 +396,9 @@ main(void) #### Serializable Snapshot Isolation -Berkeley DB also offers *serializable snapshot isolation* (SSI), a stronger mode selected with the `DB_TXN_SNAPSHOT_SAFE` flag. Plain snapshot isolation (`DB_TXN_SNAPSHOT`, above) gives each transaction a consistent view as of its start and avoids read locks, but it permits a small class of anomalies (write skew) that a fully serializable schedule would not. SSI closes that gap: it runs the transaction under snapshot isolation *and* tracks read/write anti-dependencies between concurrent snapshot-safe transactions, aborting a transaction whenever it detects a dependency cycle that could produce a non-serializable outcome. +In this fork, the `DB_TXN_SNAPSHOT` flag provides *serializable snapshot isolation* (SSI). Plain (non-serializable) snapshot isolation gives each transaction a consistent view as of its start and avoids read locks, but it permits a small class of anomalies (write skew) that a fully serializable schedule would not. SSI closes that gap: it runs the transaction under snapshot isolation *and* tracks read/write anti-dependencies between concurrent snapshot transactions, aborting a transaction whenever it detects a dependency structure that could produce a non-serializable outcome. (Stock Oracle Berkeley DB gave `DB_TXN_SNAPSHOT` only plain SI, with a separate `DB_TXN_SNAPSHOT_SAFE` flag for SSI; that flag has been removed and `DB_TXN_SNAPSHOT` is now always SSI.) -`DB_TXN_SNAPSHOT_SAFE` is a per-transaction flag; it implies `DB_TXN_SNAPSHOT` and is passed to DB_ENV->txn_begin() the same way. Unlike `DB_TXN_SNAPSHOT`, it cannot be enabled environment-wide through `DB_ENV->set_flags()` — each transaction that wants serializable snapshot isolation must request it. +`DB_TXN_SNAPSHOT` (SSI) is passed to DB_ENV->txn_begin(), or enabled environment-wide through `DB_ENV->set_flags()`. Because SSI aborts transactions to preserve serializability, a serializable-snapshot transaction may fail to commit with one of two Berkeley DB-specific return codes: @@ -407,4 +407,4 @@ Because SSI aborts transactions to preserve serializability, a serializable-snap In both cases the application must abort the transaction and may retry it, exactly as it would for `DB_LOCK_DEADLOCK`. See Error Returns to Applications for the return-code descriptions. -One restriction applies: a `DB_TXN_SNAPSHOT_SAFE` transaction cannot be prepared for two-phase commit. DB_TXN->prepare() returns `EINVAL` for such a transaction, because SSI's conflict status is not frozen at prepare time and a later-detected anomaly could not be honored once the transaction had entered the prepared state. +One restriction applies: a `DB_TXN_SNAPSHOT` (SSI) transaction cannot be prepared for two-phase commit. DB_TXN->prepare() returns `EINVAL` for such a transaction, because SSI's conflict status is not frozen at prepare time and a later-detected anomaly could not be honored once the transaction had entered the prepared state. diff --git a/docs_src/guides/programmer_reference/program_errorret.md b/docs_src/guides/programmer_reference/program_errorret.md index d440d76c5..cd7ab60e0 100644 --- a/docs_src/guides/programmer_reference/program_errorret.md +++ b/docs_src/guides/programmer_reference/program_errorret.md @@ -58,8 +58,8 @@ The DB_SECONDARY_BAD error is returned if a secondary index has been corrupted. **DB_SNAPSHOT_CONFLICT** -The DB_SNAPSHOT_CONFLICT error is returned to a transaction started with the DB_TXN_SNAPSHOT_SAFE flag (serializable snapshot isolation, SSI) when Berkeley DB detects a conflicting snapshot update — an attempt to commit an update that conflicts with another concurrent snapshot-safe transaction. The affected transaction must be aborted; the application may then retry it. This return is specific to serializable snapshot isolation and does not occur for plain DB_TXN_SNAPSHOT transactions. +The DB_SNAPSHOT_CONFLICT error is returned to a transaction started with the DB_TXN_SNAPSHOT flag (serializable snapshot isolation, SSI) when Berkeley DB detects a conflicting snapshot update — an attempt to commit an update that conflicts with another concurrent snapshot transaction. The affected transaction must be aborted; the application may then retry it. This return is specific to serializable snapshot isolation. **DB_SNAPSHOT_UNSAFE** -The DB_SNAPSHOT_UNSAFE error is returned to a transaction started with the DB_TXN_SNAPSHOT_SAFE flag (serializable snapshot isolation, SSI) when Berkeley DB detects a potential serializability anomaly through a read/write anti-dependency: the transaction sits as the pivot of a dangerous structure (it is both the read end and the write end of anti-dependency edges among committed and running transactions) that could otherwise produce a non-serializable schedule. The affected transaction is aborted rather than allowed to commit; the application may then retry it. As with DB_SNAPSHOT_CONFLICT, this return is specific to serializable snapshot isolation. +The DB_SNAPSHOT_UNSAFE error is returned to a transaction started with the DB_TXN_SNAPSHOT flag (serializable snapshot isolation, SSI) when Berkeley DB detects a potential serializability anomaly through a read/write anti-dependency: the transaction sits as the pivot of a dangerous structure (it is both the read end and the write end of anti-dependency edges among committed and running transactions) that could otherwise produce a non-serializable schedule. The affected transaction is aborted rather than allowed to commit; the application may then retry it. As with DB_SNAPSHOT_CONFLICT, this return is specific to serializable snapshot isolation. diff --git a/docs_src/guides/programmer_reference/transapp_read.md b/docs_src/guides/programmer_reference/transapp_read.md index 9667187c3..a82fe9826 100644 --- a/docs_src/guides/programmer_reference/transapp_read.md +++ b/docs_src/guides/programmer_reference/transapp_read.md @@ -39,8 +39,8 @@ If the application has update transactions which read many items and only update ### Serializable Snapshot Isolation -Snapshot isolation as configured with DB_TXN_SNAPSHOT gives each transaction a consistent view as of its start and avoids read locks, but it is not fully serializable: it admits a class of anomalies (write skew) in which two transactions each read data the other then updates. Berkeley DB adds *serializable snapshot isolation* (SSI), selected with the DB_TXN_SNAPSHOT_SAFE flag, which runs the transaction under snapshot isolation and additionally detects the read/write anti-dependency cycles that cause those anomalies, aborting a transaction rather than allowing a non-serializable schedule to commit. SSI implements the Cahill serializable-snapshot-isolation algorithm. +In this fork, snapshot isolation as configured with DB_TXN_SNAPSHOT is *serializable snapshot isolation* (SSI). Plain (non-serializable) snapshot isolation gives each transaction a consistent view as of its start and avoids read locks, but it is not fully serializable: it admits a class of anomalies (write skew) in which two transactions each read data the other then updates. SSI runs the transaction under snapshot isolation and additionally detects the read/write anti-dependency cycles that cause those anomalies, aborting a transaction rather than allowing a non-serializable schedule to commit. SSI implements the Cahill serializable-snapshot-isolation algorithm. (Stock Oracle Berkeley DB gave `DB_TXN_SNAPSHOT` only plain SI and offered SSI through a separate `DB_TXN_SNAPSHOT_SAFE` flag; that flag has been removed — `DB_TXN_SNAPSHOT` is now always SSI.) -`DB_TXN_SNAPSHOT_SAFE` implies `DB_TXN_SNAPSHOT` and is likewise passed to DB_ENV->txn_begin(); the same `DB_MULTIVERSION` database configuration applies. It is a per-transaction flag only and cannot be enabled environment-wide with DB_ENV->set_flags(). +`DB_TXN_SNAPSHOT` is passed to DB_ENV->txn_begin() (or set environment-wide with DB_ENV->set_flags()); the `DB_MULTIVERSION` database configuration applies. -A serializable-snapshot transaction that would violate serializability is aborted before commit and returns one of two Berkeley DB-specific codes: DB_SNAPSHOT_UNSAFE (a potential anomaly detected through a read/write anti-dependency) or DB_SNAPSHOT_CONFLICT (a conflicting snapshot update). The application handles these exactly as it handles DB_LOCK_DEADLOCK: abort the transaction and, if appropriate, retry it. Finally, a `DB_TXN_SNAPSHOT_SAFE` transaction cannot be prepared for two-phase commit — DB_TXN->prepare() returns `EINVAL` because SSI's conflict status is not frozen at prepare time. +A serializable-snapshot transaction that would violate serializability is aborted before commit and returns one of two Berkeley DB-specific codes: DB_SNAPSHOT_UNSAFE (a potential anomaly detected through a read/write anti-dependency) or DB_SNAPSHOT_CONFLICT (a conflicting snapshot update). The application handles these exactly as it handles DB_LOCK_DEADLOCK: abort the transaction and, if appropriate, retry it. Finally, a `DB_TXN_SNAPSHOT` (SSI) transaction cannot be prepared for two-phase commit — DB_TXN->prepare() returns `EINVAL` because SSI's conflict status is not frozen at prepare time. diff --git a/rfc/0003-ssi-serializable-snapshot-isolation.md b/rfc/0003-ssi-serializable-snapshot-isolation.md index e3a4b6af5..19b50ad56 100644 --- a/rfc/0003-ssi-serializable-snapshot-isolation.md +++ b/rfc/0003-ssi-serializable-snapshot-isolation.md @@ -9,9 +9,18 @@ --- +> **Amendment (2026, post-implementation):** the public API was simplified. SSI +> is no longer a separate `DB_TXN_SNAPSHOT_SAFE` flag — that flag was **removed** +> and its behavior folded into **`DB_TXN_SNAPSHOT`**, which is now always +> serializable. There is no separate plain (non-serializable) snapshot-isolation +> mode in the public API. This is a deliberate ABI break, accepted to avoid the +> awkward `_SAFE` flag name. Everything below describing `DB_TXN_SNAPSHOT_SAFE` +> now applies to `DB_TXN_SNAPSHOT`; the internal `TXN_SNAPSHOT_SAFE` state and +> the SSI machinery are unchanged. + ## Summary -An opt-in `DB_TXN_SNAPSHOT_SAFE` transaction mode that provides full +The `DB_TXN_SNAPSHOT` transaction mode provides full serializable isolation on top of MVCC snapshot isolation, using Michael Cahill's Serializable Snapshot Isolation algorithm: detect the dangerous read/write dependency structures that let snapshot isolation admit @@ -36,8 +45,9 @@ without a server and without giving up embedded operation. - Multi-process correctness: SIREAD markers/lockers live in the shared lock region; the concurrent-writer lifetime is hardened (see the M2/M4 notes) and guarded by `ssi009` (multi-process stress). -- On-disk/log/region/ABI: no on-disk or log format change. `DB_TXN_SNAPSHOT_SAFE` - is a new flag; `prepare()`/2PC rejects it. +- On-disk/log/region/ABI: no on-disk or log format change. `DB_TXN_SNAPSHOT` + is now the SSI mode (the separate `DB_TXN_SNAPSHOT_SAFE` flag was removed — a + deliberate ABI break); `prepare()`/2PC rejects an SSI transaction. ## Design diff --git a/rfc/INDEX.md b/rfc/INDEX.md index f90604dc3..f3d6328d2 100644 --- a/rfc/INDEX.md +++ b/rfc/INDEX.md @@ -7,6 +7,6 @@ process. Status: Draft · Accepted · Rejected · Superseded · Implemented. |---|-------|--------|------| | [0001](0001-adaptive-lsm.md) | Adaptive LSM access method (HanoiDB + segment-policy, Bitcask, index-in-WAL) | Draft | Prospective | | [0002](0002-buffer-swip-aio.md) | Scalable buffer access: tagged swip, optimistic descent, async I/O | Draft | Prospective | -| [0003](0003-ssi-serializable-snapshot-isolation.md) | Serializable Snapshot Isolation (SSI): `DB_TXN_SNAPSHOT_SAFE`, rw-antidependency detection | Implemented | Normative | +| [0003](0003-ssi-serializable-snapshot-isolation.md) | Serializable Snapshot Isolation (SSI): `DB_TXN_SNAPSHOT` is serializable, rw-antidependency detection | Implemented | Normative | diff --git a/test/bench/ssi_abort_bench.c b/test/bench/ssi_abort_bench.c index a463814d9..b9b6949c8 100644 --- a/test/bench/ssi_abort_bench.c +++ b/test/bench/ssi_abort_bench.c @@ -1,7 +1,7 @@ /*- * libdb SSI abort-rate probe. * - * Serializable Snapshot Isolation (DB_TXN_SNAPSHOT_SAFE) prevents write-skew + * Serializable Snapshot Isolation (DB_TXN_SNAPSHOT) prevents write-skew * and other snapshot anomalies by aborting the pivot of a dangerous rw- * dependency structure. The cost is a nonzero abort rate that grows with * contention, and -- because Berkeley DB tracks conflicts at PAGE granularity, @@ -60,7 +60,7 @@ one_txn(targ_t *t) rk = rand_r(&t->seed) % hotkeys; wk = rand_r(&t->seed) % hotkeys; - if (env->txn_begin(env, NULL, &txn, DB_TXN_SNAPSHOT_SAFE) != 0) { + if (env->txn_begin(env, NULL, &txn, DB_TXN_SNAPSHOT) != 0) { t->other++; return; }