◐ Off-By-One · answer catalog

mvcc-snapshot-isolation

1 answer(s)pythonpython3

def installversion(self, key, value, committs):

📦 Source in repository (JSON)

Answer

The implementation (mvcc_store.py) builds a thread-safe MVCC key-value store with three core components:

1. Versioned Storage

Each key maps to a sorted list of Version objects. Every version carries: - value – the stored data - begin_ts – commit timestamp when this version was created - end_ts – timestamp when this version was superseded (or ∞ for the latest)

A transaction with start_ts = S reads the version with the largest begin_ts ≤ S and S < end_ts. Writes are buffered locally until commit.

2. First-Committer-Wins Conflict Detection

On commit, for every key in the transaction's write-set, the store checks whether any committed version has begin_ts > start_ts. If so, a concurrent transaction already wrote to that key and committed first → the current transaction aborts. This is checked atomically under a global lock.

3. Garbage Collection

GC computes min_active_start_ts (the smallest start_ts among all active transactions). A version is safe to remove when end_ts ≤ min_active_start_ts because no active snapshot can see it. As a safety net, the latest version per key is always preserved so the key list never becomes empty (every key retains at least a tombstone history).

"""mvcc_store.py – MVCC key-value store with snapshot isolation."""

import threading
import uuid

INF = float("inf")

class Version:
    __slots__ = ("value", "begin_ts", "end_ts")
    def __init__(self, value, begin_ts, end_ts=INF):
        self.value = value
        self.begin_ts = begin_ts
        self.end_ts = end_ts

class MVCCStore:
    def __init__(self):
        self._lock = threading.Lock()
        self._data = {}          # key -> [Version]  (sorted by begin_ts)
        self._clock = 0
        self._active_txns = {}   # id -> Transaction

    def _next_ts(self):
        self._clock += 1
        return self._clock

    def _min_active_start_ts(self):
        min_ts = INF
        for t in self._active_txns.values():
            if t.status == "active":
                min_ts = min(min_ts, t.start_ts)
        return min_ts

    def _read_version(self, key, start_ts):
        versions = self._data.get(key, [])
        if not versions:
            return None
        # binary search for rightmost begin_ts <= start_ts
        lo, hi, idx = 0, len(versions) - 1, -1
        while lo <= hi:
            mid = (lo + hi) // 2
            if versions[mid].begin_ts <= start_ts:
                idx, lo = mid, mid + 1
            else:
                hi = mid - 1
        if idx == -1:
            return None
        c = versions[idx]
        return c if start_ts < c.end_ts else None

    def _install_version(self, key, value, commit_ts):
        if key not in self._data:
            self._data[key] = []
        vs = self._data[key]
        if vs:
            vs[-1].end_ts = commit_ts
        vs.append(Version(value, commit_ts))

    def _has_write_conflict(self, keys, start_ts):
        for key in keys:
            vs = self._data.get(key, [])
            if vs and vs[-1].begin_ts > start_ts:
                return True
        return False

    def begin(self):
        t = Transaction(self, self._next_ts())
        with self._lock:
            self._active_txns[t.id] = t
        return t

    def gc(self):
        with self._lock:
            min_ts = self._min_active_start_ts()
            for key in list(self._data.keys()):
                kept = [v for v in self._data[key] if v.end_ts > min_ts]
                if not kept and self._data[key]:
                    kept.append(self._data[key][-1])
                self._data[key] = kept

class Transaction:
    def __init__(self, store, start_ts):
        self.id = uuid.uuid4().hex
        self.store = store
        self.start_ts = start_ts
        self.status = "active"
        self._write_set = {}

    def read(self, key):
        v = self.store._read_version(key, self.start_ts)
        return v.value if v else None

    def write(self, key, value):
        self._write_set[key] = value

    def commit(self):
        if self.status != "active":
            return False
        with self.store._lock:
            if self.store._has_write_conflict(self._write_set.keys(), self.start_ts):
                self.status = "aborted"
                self.store._active_txns.pop(self.id, None)
                return False
            commit_ts = self.store._next_ts()
            for k, v in self._write_set.items():
                self.store._install_version(k, v, commit_ts)
            self.status = "committed"
            self.store._active_txns.pop(self.id, None)
        self.store.gc()
        return True

    def rollback(self):
        if self.status != "active":
            return
        self.status = "aborted"
        with self.store._lock:
            self.store._active_txns.pop(self.id, None)

Key design decisions: - Binary search for _read_version gives O(log N) lookup per key. - Global lock simplifies concurrency; contention is minimal for KV workloads. - GC triggered on every commit keeps the version list tidy without a background thread. - Empty-list guard ensures every key has at least one version post-GC, preserving correct deletion/non-existence semantics.


Evidence & signatures

### Test results (32/32 passing)

```
  PASS: test_basic_read_write                    PASS: test_snapshot_isolation
  PASS: test_first_committer_wins                PASS: test_no_conflict_different_keys
  PASS: test_read_only_no_conflict               PASS: test_rollback
  PASS: test_sequential_updates                  PASS: test_gc_basic
  PASS: test_gc_with_active_txn                  PASS: test_gc_removes_old_versions
  PASS: test_gc_mid_snapshot                     PASS: test_delete_tombstone
  PASS: test_concurrent_threads                  PASS: test_snapshot_across_concurrent_writes
  PASS: test_gc_after_all_txns_done              PASS: test_many_keys_no_conflict
  PASS: test_idempotent_gc                       PASS: test_gc_empty_store
  PASS: test_aborted_transaction_no_effect       PASS: test_commit_after_rollback_fails
  PASS: test_snapshot_with_overlapping_keys      PASS: test_concurrent_same_key_three_way
  PASS: test_first_committer_wins_ordering       PASS: test_gc_preserves_latest
  PASS: test_end_ts_exact_boundary               PASS: test_read_key_with_no_versions
  PASS: test_write_then_read_same_txn            PASS: test_gc_during_active_txn_keeps_correct_version
  PASS: test_gc_all_versions_removed_preserves_latest
  PASS: test_concurrent_write_then_read_consistency
  PASS: test_multiple_gc_preserves_invariants
  PASS: test_highly_concurrent_no_deadlock
```

### Edge cases tested

| Category | Specific case |
|---|---|
| **Snapshot isolation** | Long-running reader sees original snapshot despite concurrent writers. Reader started *before* a key exists sees `None` even after the key is created. |
| **First-committer-wins** | Two concurrent writers → first succeeds, second aborts. Three concurrent writers → only one succeeds. Transaction that starts earlier but commits later aborts (commit order, not start order). |
| **Non-conflicting** | Disjoint key sets all commit. Read-only transactions never conflict. |
| **GC correctness** | GC removes only versions with `end_ts ≤ min_active_start_ts`. Active transaction's visible version survives GC. Repeated/idempotent GC calls. Empty store GC is a no-op. Latest version always preserved even when all versions are stale. |
| **Boundary** | `end_ts == start_ts` → version not visible (strict inequality). |
| **Tombstones** | Writing `None` creates a proper tombstone; subsequent reads see `None`. |
| **Concurrency** | 10 threads × 10 iterations on same hot key → no deadlocks, correct abort semantics. |
| **Cleanup** | Rollback leaves no trace. Double-commit returns `False`. |

---
{"model": "claude-3.5-sonnet-v2", "problem_class": "mvcc-snapshot-isolation", "result": "passed", "tests": 32}
Generated from the verified corpus · MIT licensedBack to the catalog