# Isolation

> SmashTable for Python

A container names what it promises a reader, and reports back what it actually delivers:

```python
cache = st.SortedMap(key=int)                                    # monotonic atomic view, the default
ledger = st.SortedMap(key=int, isolation='snapshot')
books = st.SortedMap(key=int, isolation='serializable')
audit = st.SortedMap(key=int, isolation='strict_serializable')

cache.isolation    # 'monotonic_atomic_view'
ledger.isolation   # 'snapshot'
books.isolation    # 'serializable'
audit.isolation    # 'strict_serializable'
```

Four names, refused by `ValueError` if misspelled — and the shipped type stubs turn a misspelling into a checker error before it ever runs.

Under `snapshot` every read a transaction makes is answered at the instant the transaction opened, so a repeated read returns what it first saw:

```python
with st.transaction(ledger) as (view,):
    first = view[account]
    # ... another thread commits a new value for `account` here ...
    assert view[account] == first     # holds under snapshot, not under monotonic
```

That is what `monotonic` does not give you, and the reason to pay for the extra versions.
The cost is memory: a key keeps every version a live reader can still name, and the tail is freed once the last transaction closes.

Snapshot validates what a transaction __wrote__, plus any key it explicitly `watch`ed, and nothing more, so two transactions that each read what the other writes and then write elsewhere both land — [write skew](https://jepsen.io/consistency/phenomena/a5b), which Snapshot Isolation permits by construction because no two writes ever collide.
`serializable` validates the read set as well, which is also what makes a [phantom](https://jepsen.io/consistency/phenomena/a3) detectable: a window a transaction scanned is remembered as a window, so a key committed into it afterwards is a conflict even though no key either transaction touched overlaps.

```python
with st.transaction(books) as (view,):
    empty = view.scan(20, 80)          # reads a window, and records that it did
    # ... another thread commits key 50 here ...
    ...                                # PhantomConflictError on leaving the block
```

`PhantomConflictError` derives from `ConflictError`, so a caller that retries on conflict needs no new branch.
`strict_serializable` refuses exactly what `serializable` refuses; it adds only that a transaction opening after a commit returned cannot be ordered before it.

`sharing` decides how many writers can proceed at once:

```python
st.SortedMap(key=int, sharing='locked')        # one lock, the default
st.SortedMap(key=int, sharing='partitioned')   # sixteen partitions, concurrent writers
```

`isolation` reports what a container delivers, not what it was asked for, and sharding is the one place those differ:

```python
sharded_snapshot = st.SortedMap(key=int, isolation='snapshot', sharing='partitioned')
sharded_monotonic = st.SortedMap(key=int, isolation='monotonic_atomic_view', sharing='partitioned')

sharded_snapshot.isolation    # 'snapshot'        — asked for snapshot, got snapshot
sharded_monotonic.isolation   # 'read_committed'  — asked for monotonic, got less
```

A monotonic container has no stamp for a reader to hold, so a walk crossing partitions can catch a commit half-applied, and only [Read Committed](https://jepsen.io/consistency/models/read-committed) survives above a single key.
A snapshot container does have one — visibility there is a comparison against a stamp rather than a lock somebody holds — so its commits become visible everywhere at once and its level survives however many partitions it is spread across.

Keeping the guarantee is not free, though.
Making a commit atomic across partitions costs in proportion to how many of them a transaction touches: one writing a single key is unaffected, while one writing across all sixteen serializes against every other transaction that does the same.
Sharding pays off for point-heavy work spread over many keys, and least for transactions that touch everything.

`locked` is the default because it is the setting under which the default level means what it says.

`import smashtable`

## PhantomConflictError

A key was committed into a window this transaction had read.

```python
class PhantomConflictError(ConflictError)
```

