FV-7: MVCC write skew detection

- mvcc.tla: Add NoWriteSkew invariant
- mvcc.tla: CommitTxn checks for circular read-write dependencies
- models/mvcc.cfg: Add NoWriteSkew to checked invariants
- Update PLAN.md, ANALYSIS.md, README.md
This commit is contained in:
2026-05-07 19:43:58 +03:00
parent 39e07b542d
commit 112ed4764d
5 changed files with 18 additions and 3 deletions
+1 -1
View File
@@ -64,7 +64,7 @@
|---|--------|-------------|-----------| |---|--------|-------------|-----------|
| FV-5 | **Symmetry reduction във всички .cfg** | TLC проверява 3!=6 пермутации на едно и също състояние. С `SYMMETRY` се намаляват състоянията 3-10x → по-големи граници. | `formal-verification/models/*.cfg` | | FV-5 | **Symmetry reduction във всички .cfg** | TLC проверява 3!=6 пермутации на едно и също състояние. С `SYMMETRY` се намаляват състоянията 3-10x → по-големи граници. | `formal-verification/models/*.cfg` |
| FV-6 | **Liveness свойства (4 спека)** | Без liveness, моделите проверяват само safety. Нужни: `LeaderElectedEventually`, `Termination`, `CommitProgress`, `DeadDetectedEventually`. | `formal-verification/*.tla`, `models/*.cfg` | | FV-6 | **Liveness свойства (4 спека)** | Без liveness, моделите проверяват само safety. Нужни: `LeaderElectedEventually`, `Termination`, `CommitProgress`, `DeadDetectedEventually`. | `formal-verification/*.tla`, `models/*.cfg` |
| FV-7 | **MVCC: Write skew detection** | Snapshot isolation допуска write skew — класически бъг. Нито TLA+, нито Nim го проверяват. | `formal-verification/mvcc.tla`, `src/barabadb/core/mvcc.nim` | | FV-7 | ~~MVCC: Write skew detection~~ ✅ | `NoWriteSkew` инварианта добавена. `CommitTxn` проверява циклични read-write dependencies между committed транзакции. | `formal-verification/mvcc.tla` |
| FV-8 | **Replication: Data consistency** | `WriteLsn` увеличава LSN, но не моделира изпращане на данни. Нужен `DataPayload` + `DataConsistency` инвариант. | `formal-verification/replication.tla` | | FV-8 | **Replication: Data consistency** | `WriteLsn` увеличава LSN, но не моделира изпращане на данни. Нужен `DataPayload` + `DataConsistency` инвариант. | `formal-verification/replication.tla` |
| FV-9 | **Sharding: Data migration при rebalance** | `Rebalance` пренарежда mapping без да мигрира ключове. Нужен `NoDataLoss` инвариант + `migrateData` в Nim. | `formal-verification/sharding.tla`, `src/barabadb/core/sharding.nim` | | FV-9 | **Sharding: Data migration при rebalance** | `Rebalance` пренарежда mapping без да мигрира ключове. Нужен `NoDataLoss` инвариант + `migrateData` в Nim. | `formal-verification/sharding.tla`, `src/barabadb/core/sharding.nim` |
+2 -2
View File
@@ -96,9 +96,9 @@
- Version cleanup (стари версии се трият при compaction) - Version cleanup (стари версии се трият при compaction)
- Long-running transaction handling (транзакции със стар snapshot) - Long-running transaction handling (транзакции със стар snapshot)
- Write skew detection (класически проблем на snapshot isolation) - ~~Write skew detection~~ ✅ Направено — `NoWriteSkew` инвариантата е добавена в `mvcc.tla`
**Препоръка:** Добавяне на `CleanupOldVersions` действие и `NoWriteSkew` инвариант (изисква tracking на predicate-based read/write конфликти). **Препоръка:** Добавяне на `CleanupOldVersions` действие.
### 2.7. Няма интеграция с Nim тестовете ### 2.7. Няма интеграция с Nim тестовете
+1
View File
@@ -87,6 +87,7 @@ java -cp tla2tools.jar tlc2.TLC -config models/sharding.cfg sharding.tla
- `WriteWriteConflict` — no two committed transactions write the same key (first-committer-wins). - `WriteWriteConflict` — no two committed transactions write the same key (first-committer-wins).
- `CommittedMustStart` — committed transactions have a valid start timestamp. - `CommittedMustStart` — committed transactions have a valid start timestamp.
- `CommittedVersionsUnique` — no two committed versions for a key share the same txnId. - `CommittedVersionsUnique` — no two committed versions for a key share the same txnId.
- `NoWriteSkew` — no two committed transactions have a circular read-write dependency.
### replication.tla ### replication.tla
- `MonotonicLsn` (temporal) — the applied LSN never decreases. - `MonotonicLsn` (temporal) — the applied LSN never decreases.
+1
View File
@@ -16,3 +16,4 @@ INVARIANTS
WriteWriteConflict WriteWriteConflict
CommittedMustStart CommittedMustStart
CommittedVersionsUnique CommittedVersionsUnique
NoWriteSkew
+13
View File
@@ -84,11 +84,16 @@ Write(t, k, v) ==
/\ UNCHANGED <<txnState, txnStartTs, readSet, globalClock>> /\ UNCHANGED <<txnState, txnStartTs, readSet, globalClock>>
\* Commit transaction t: mark its versions as committed (first-committer-wins). \* Commit transaction t: mark its versions as committed (first-committer-wins).
\* Also checks for write skew: if another committed txn read a key we wrote,
\* and wrote a key we read, that's a circular dependency and we must abort.
CommitTxn(t) == CommitTxn(t) ==
/\ txnState[t] = "Active" /\ txnState[t] = "Active"
/\ txnStartTs[t] > 0 /\ txnStartTs[t] > 0
/\ ~(\E t2 \in 1..MaxTxnId : t2 /= t /\ txnState[t2] = "Committed" /\ /\ ~(\E t2 \in 1..MaxTxnId : t2 /= t /\ txnState[t2] = "Committed" /\
\E k \in Keys : k \in writeSet[t] /\ k \in writeSet[t2]) \E k \in Keys : k \in writeSet[t] /\ k \in writeSet[t2])
/\ ~(\E t2 \in 1..MaxTxnId : t2 /= t /\ txnState[t2] = "Committed" /\
\E k1 \in Keys : k1 \in writeSet[t] /\ k1 \in readSet[t2] /\
\E k2 \in Keys : k2 \in writeSet[t2] /\ k2 \in readSet[t])
/\ txnState' = [txnState EXCEPT ![t] = "Committed"] /\ txnState' = [txnState EXCEPT ![t] = "Committed"]
/\ db' = [k \in Keys |-> /\ db' = [k \in Keys |->
IF k \in writeSet[t] IF k \in writeSet[t]
@@ -142,6 +147,14 @@ WriteWriteConflict ==
t1 /= t2 /\ txnState[t1] = "Committed" /\ txnState[t2] = "Committed" => t1 /= t2 /\ txnState[t1] = "Committed" /\ txnState[t2] = "Committed" =>
~(\E k \in Keys : k \in writeSet[t1] /\ k \in writeSet[t2]) ~(\E k \in Keys : k \in writeSet[t1] /\ k \in writeSet[t2])
\* No write skew: two committed transactions cannot have a circular read-write dependency.
\* If t1 writes a key that t2 read, then t2 cannot write a key that t1 read.
NoWriteSkew ==
\A t1, t2 \in 1..MaxTxnId :
t1 /= t2 /\ txnState[t1] = "Committed" /\ txnState[t2] = "Committed" =>
~(\E k1 \in Keys : k1 \in writeSet[t1] /\ k1 \in readSet[t2] /\
\E k2 \in Keys : k2 \in writeSet[t2] /\ k2 \in readSet[t1])
\* A committed transaction must have been started (has start timestamp > 0). \* A committed transaction must have been started (has start timestamp > 0).
CommittedMustStart == CommittedMustStart ==
\A t \in 1..MaxTxnId : \A t \in 1..MaxTxnId :