Skip to content

Commit 32ff40c

Browse files
committed
mostly functional
1 parent b549d36 commit 32ff40c

10 files changed

Lines changed: 2317 additions & 409 deletions

File tree

LMDB_ISOLATION_EXECPLAN.md

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# LMDB-Backed Isolation Up To Snapshot
2+
3+
This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds.
4+
5+
This plan must be maintained in accordance with `PLANS.md` at the repository root.
6+
7+
## Purpose / Big Picture
8+
9+
After this change, the LMDB Store will rely on LMDB's own transaction isolation for NONE, READ_COMMITTED, SNAPSHOT_READ, and SNAPSHOT. Snapshot and repeatable-read behavior will come from LMDB read transactions rather than the current in-memory SnapshotSailStore. Users will see the same externally observable isolation behavior (as proven by the existing isolation tests), but with simpler transaction mechanics and fewer in-memory overlays.
10+
11+
## Progress
12+
13+
- [x] (2026-01-02 09:45Z) Drafted ExecPlan and initial analysis.
14+
- [x] (2026-01-02 13:30Z) Defined LMDB transaction context and APIs.
15+
- [x] (2026-01-02 14:25Z) Implemented LMDB-backed SailSource/Sink/Dataset behavior.
16+
- [x] (2026-01-02 14:25Z) Updated isolation level support and lock strategy.
17+
- [x] (2026-01-02 13:59Z) Enforced thread-affine LMDB txn context use.
18+
- [x] (2026-01-02 15:01Z) Ran LmdbStoreIsolationLevelTest and LmdbSailStoreTest.
19+
- [x] (2026-01-02 15:18Z) Added commit read-lock + pinned ValueStore reads.
20+
- [x] (2026-01-02 15:25Z) Added commit-window regression test and commit/read lock guard.
21+
- [x] (2026-01-02 21:35Z) Added SNAPSHOT begin/commit regression test.
22+
- [x] (2026-01-02 21:45Z) Guarded snapshot txn pinning with commit read lock.
23+
- [x] (2026-01-03 11:39Z) Wired SNAPSHOT_READ pinning and deferred updates in LmdbTxnContext.
24+
- [x] (2026-01-03 11:45Z) Aligned deprecated tracking with pending update buffering.
25+
- [x] (2026-01-03 11:54Z) Treated SNAPSHOT reads as pinned + deferred to keep snapshotRead stable.
26+
- [x] (2026-01-03 13:30Z) Added serializable conflict tracking + prepare checks for LMDB snapshots.
27+
- [x] (2026-01-03 13:34Z) LmdbOptimisticIsolationTest green (mvnf core/sail/lmdb LmdbOptimisticIsolationTest).
28+
- [ ] Run full LMDB module verify and isolation suite.
29+
30+
## Surprises & Discoveries
31+
32+
- Observation: LmdbStore wraps the backend with SnapshotSailStore and bypasses it only when isolation is disabled, so snapshot semantics are not coming from LMDB.
33+
Evidence:
34+
core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java
35+
this.store = new SnapshotSailStore(backingStore, ...)
36+
if (isIsolationDisabled()) { return backingStore.getExplicitSailSource(); }
37+
- Observation: LmdbSailSource overrides fork() and does not use SailSourceBranch; prepare() on the source is a no-op unless we wire serializable conflict checks directly into LmdbSailSource/LmdbTxnContext.
38+
Evidence:
39+
core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStore.java
40+
LmdbSailSource.fork() returns a new LmdbSailSource (bypassing SailSourceBranch)
41+
LmdbSailSource.prepare() now performs serializable conflict checks
42+
- Observation: LmdbSailSink.flush() commits LMDB transactions immediately, which would prematurely commit changes if SnapshotSailStore were removed.
43+
Evidence:
44+
core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStore.java
45+
tripleStore.commit(); ... valueStore.commit();
46+
- Observation: Triple store writes can run on a background thread, so the write transaction is not on the caller thread; LMDB write transactions are thread-affine.
47+
Evidence:
48+
core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStore.java
49+
tripleStoreExecutor.submit(() -> { tripleStore.startTransaction(); ... });
50+
- Observation: SPARQL MODIFY updates must not see their own writes while iterating WHERE bindings.
51+
Evidence:
52+
testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/DeleteInsertTest.java
53+
DeleteInsertTest initially failed until per-update changes were buffered.
54+
- Observation: LMDB module verify appears to stall in OptimisticIsolationTest (IsolationLevelTest logging).
55+
Evidence:
56+
logs/mvnf/20260102-213127-verify.log (no completion after IsolationLevelTest stack trace).
57+
core/sail/lmdb/target/surefire-reports/2026-01-02T22-31-54_127-jvmRun1.dumpstream
58+
59+
## Decision Log
60+
61+
- Decision: Implement LMDB-native isolation for NONE, READ_COMMITTED, SNAPSHOT_READ, SNAPSHOT and stop using SnapshotSailStore for those levels.
62+
Rationale: The requirement is to rely solely on LMDB isolation up to SNAPSHOT, and the current SnapshotSailStore overlay prevents that.
63+
Date/Author: 2026-01-02 / Codex
64+
- Decision: Implement SERIALIZABLE via optimistic conflict detection over LMDB snapshots.
65+
Rationale: Track observed statement patterns and compare pinned snapshot vs current state at prepare/commit; this preserves serializable semantics without a SnapshotSailStore overlay.
66+
Date/Author: 2026-01-03 / Codex
67+
- Decision: Disable async triple-store write threading for transactions that need read-your-writes.
68+
Rationale: LMDB write transactions are thread-affine; read-your-writes requires reading from the same write transaction.
69+
Date/Author: 2026-01-02 / Codex
70+
- Decision: Buffer per-update changes and merge on endUpdate to keep MODIFY WHERE evaluation stable.
71+
Rationale: SPARQL updates must not observe their own modifications while streaming bindings.
72+
Date/Author: 2026-01-02 / Codex
73+
- Decision: Bind LMDB transaction contexts to the thread that opens pinned/read-write txns; reject cross-thread access.
74+
Rationale: LMDB read/write transactions are thread-affine, so snapshot or write transactions must stay on one thread.
75+
Date/Author: 2026-01-02 / Codex
76+
- Decision: Block dataset creation during commit and pin ValueStore reads per dataset.
77+
Rationale: Avoid a window where triple-store commits become visible before the ValueStore commit, leading to unresolved IDs.
78+
Date/Author: 2026-01-02 / Codex
79+
- Decision: Split SNAPSHOT_READ vs SNAPSHOT handling in LmdbTxnContext using explicit mode flags.
80+
Rationale: Defer writes for SNAPSHOT_READ while pinning read snapshots, and eagerly start write transactions for SNAPSHOT to keep snapshot boundaries deterministic.
81+
Date/Author: 2026-01-03 / Codex
82+
- Decision: Track deprecated removals per pending update before merging into transaction-wide state.
83+
Rationale: Aborted updates must not leak deprecations into later change visibility or connection listener notifications.
84+
Date/Author: 2026-01-03 / Codex
85+
- Decision: For SNAPSHOT, pin read snapshots and defer writes like SNAPSHOT_READ.
86+
Rationale: SnapshotRead tests require stable iterators; deferring writes avoids mid-iteration visibility while preserving read-your-writes via overlays.
87+
Date/Author: 2026-01-03 / Codex
88+
89+
## Outcomes & Retrospective
90+
91+
Implemented LMDB-backed transaction context, forkable sources, and snapshot handling without SnapshotSailStore. Added serializable conflict detection based on observed patterns against pinned snapshots, with prepare-time checks for LMDB-backed branches. Remaining work: run full LMDB module verify and isolation suite before final acceptance.
92+
93+
## Context and Orientation
94+
95+
The LMDB store implementation lives under core/sail/lmdb. LmdbStore is the Sail entry point. It currently wraps LmdbSailStore in SnapshotSailStore, which uses in-memory changesets to provide snapshot semantics. SailSourceConnection in core/sail/base uses SailSource.fork() to create transaction-scoped branches when isolation is not NONE, so the LMDB store must implement a forking SailSource if it is used directly. LmdbSailSink and LmdbSailDataset are the LMDB-specific write and read adapters. TxnManager and ValueStore manage LMDB read/write transactions.
96+
97+
Definitions used here:
98+
- LMDB environment means the LMDB database handle opened by mdb_env_open (the value store and triple store each use separate environments).
99+
- Snapshot isolation means repeatable reads within a transaction and no visibility of concurrent commits after the transaction begins.
100+
- Pinned read transaction means a long-lived LMDB read transaction held open for the duration of a Sail transaction.
101+
102+
## Plan of Work
103+
104+
First, define an LMDB transaction context that is created when a Sail transaction begins and closed on commit or rollback. This context must own the LMDB read transaction used for repeatable reads (SNAPSHOT, SNAPSHOT_READ) and, when writes occur, the LMDB write transactions for the value store and triple store. It must also provide a consistent story for NONE and READ_COMMITTED: reads use short-lived read transactions unless a write transaction is active, in which case reads must use the write transaction so that read-your-writes works.
105+
106+
Next, make LmdbSailSource forkable and context-aware. A forked LmdbSailSource should bind to the transaction context so that datasets and sinks created from it use the same read/write transactions. LmdbSailSink.flush() must not commit for transaction-scoped sinks; commit should occur once per Sail transaction at SailSource.flush() or on connection commit. This removes the need for SnapshotSailStore to buffer changes in memory.
107+
108+
Then, update the LMDB transaction machinery. TxnManager must support pinned read transactions that are not reset on every commit, and ValueStore must allow a pinned read transaction for snapshot transactions instead of its current per-call renew/reset behavior. Ensure map-resize logic does not invalidate pinned snapshot transactions; if it must, detect and fail those transactions with a clear conflict error.
109+
110+
Finally, update LmdbStore to remove the SnapshotSailStore wrapper for SNAPSHOT and below, adjust supported isolation levels, and run the isolation and LMDB module tests to validate behavior.
111+
112+
## Concrete Steps
113+
114+
All commands are from the repository root.
115+
116+
1) Baseline build (required before tests):
117+
mvn -T 1C -o -Dmaven.repo.local=.m2_repo -Pquick clean install | tail -200
118+
119+
2) If adding or adjusting tests, run the smallest targeted test first:
120+
python3 .codex/skills/mvnf/scripts/mvnf.py LmdbStoreIsolationLevelTest
121+
122+
Expected tail excerpt contains "BUILD SUCCESS".
123+
124+
3) Run LMDB module verification after implementation:
125+
python3 .codex/skills/mvnf/scripts/mvnf.py core/sail/lmdb
126+
127+
4) If module tests pass but isolation semantics are still in doubt, run the Sail isolation suite explicitly:
128+
python3 .codex/skills/mvnf/scripts/mvnf.py SailIsolationLevelTest
129+
130+
If any command fails because of missing offline artifacts, rerun the same command once without -o, then return to offline runs.
131+
132+
## Validation and Acceptance
133+
134+
Acceptance is met when:
135+
- LmdbStoreIsolationLevelTest passes for NONE, READ_COMMITTED, SNAPSHOT_READ, and SNAPSHOT.
136+
- Snapshot semantics are visible: repeated reads inside a transaction are stable; concurrent commits do not appear mid-transaction; read-your-writes works.
137+
- The LMDB module test run core/sail/lmdb is green.
138+
- LmdbStore no longer relies on SnapshotSailStore for the supported isolation levels, and LmdbSailSource.fork() is implemented.
139+
140+
If SERIALIZABLE is removed, tests should skip it by reporting it as unsupported.
141+
142+
## Idempotence and Recovery
143+
144+
All steps are repeatable. If a change causes isolation tests to fail, revert only the LMDB module changes, keep any new tests, and iterate. If map resize conflicts with pinned read transactions, return a clear SailConflictException and add a targeted test to document the behavior.
145+
146+
## Artifacts and Notes
147+
148+
Collect short snippets (no more than a few lines) from:
149+
- core/sail/lmdb/target/surefire-reports/ showing passing isolation tests.
150+
- Any new LMDB-specific tests added for snapshot or read-your-writes behavior.
151+
152+
Example (placeholder):
153+
Tests run: 12, Failures: 0, Errors: 0, Skipped: 0
154+
155+
## Interfaces and Dependencies
156+
157+
New or adjusted LMDB-facing APIs should live in core/sail/lmdb:
158+
159+
- New class org.eclipse.rdf4j.sail.lmdb.LmdbTxnContext
160+
Responsibilities: track isolation level, hold pinned read transactions, and manage write transaction lifecycle across TripleStore and ValueStore.
161+
Required methods (names can be adjusted as long as intent is preserved):
162+
- void begin(IsolationLevel level)
163+
- Txn acquireTripleReadTxn() (returns pinned or per-call based on level)
164+
- long acquireTripleWriteTxn() (write transaction handle on the caller thread)
165+
- ValueStore.ReadTxn acquireValueReadTxn() (pinned or per-call)
166+
- void markWriteStarted()
167+
- void commit()
168+
- void rollback()
169+
- void close()
170+
171+
- LmdbSailStore.LmdbSailSource
172+
Implement fork() and accept an optional LmdbTxnContext.
173+
dataset(IsolationLevel) must use context-provided read transactions.
174+
sink(IsolationLevel) must create sinks that write into context-managed LMDB write transactions.
175+
176+
- LmdbSailStore.LmdbSailSink
177+
Allow a transaction-scoped mode where flush() does not commit; commit is handled by the enclosing SailSource.flush() or connection commit.
178+
179+
- TxnManager
180+
Add a pinned read transaction path (not reset on commit) or a way to exclude active snapshot transactions from reset().
181+
182+
- ValueStore
183+
Add a pinned read transaction API (similar semantics to TxnManager) and a method to run reads against that pinned transaction.
184+
185+
- LmdbStore
186+
Remove the SnapshotSailStore wrapper for supported isolation levels and update setSupportedIsolationLevels(...) to stop claiming SERIALIZABLE if it is not implemented.
187+
188+
Each new or changed API should be documented inline with a short comment explaining how it supports LMDB-backed snapshot isolation.
189+
190+
## Plan Change Note
191+
192+
(2026-01-02) Created the initial ExecPlan document from repository analysis so implementation can proceed with a living plan.
193+
(2026-01-03) Updated progress and decisions to reflect snapshot-read pinning and update deferral work in LmdbTxnContext.
194+
(2026-01-03) Recorded deprecated-tracking alignment in progress and decision log.
195+
(2026-01-03) Documented SNAPSHOT deferral decision to satisfy snapshotRead stability.
196+
(2026-01-03) Updated serializable approach and prepare-time conflict checks after LmdbOptimisticIsolationTest coverage.

0 commit comments

Comments
 (0)