CSE-4/562 Spring 2019 - Optimistic Concurrency Control

Optimistic Concurrency Control

CSE-4/562 Spring 2019

April 3, 2019

Textbook: Ch. 18.8-18.9
Schedule
A sequence of read and writes from one or more transactions to objects
Serial Schedule
A schedule with no interleaving
... but with an arbitrary transaction order
Serializable Schedule
A schedule that produces the same output as a serial schedule

Forcing Acyclicity

Locking
Snapshot Isolation
Timestamp Concurrency Control*
TimeT1T2
| W(A)
| W(A)
| W(B)
| W(B)
| W(C)
W(C)
 

3x T2 "happens before" T1

The schedule is conflict-serializable

... but 2-Phase locking won't work if we can't predict T1's accesses.

Locking is...

... expensive
Costs are still incurred even if there are no problematic conflicts.
... restrictive
We don't know what the transaction will do, so we can't allow all schedules.

We don't know what a transaction will do... until it does.

Idea: Let the transaction do it.

(Then fix it if it broke anything later)

Forcing Acyclicity

Locking
Pessimistically block transactions before they can operate out of order.
Snapshot Isolation
Optimistically allow transactions to run and clean up after them if they break things.
Timestamp Concurrency Control*

Snapshot Isolation

Phase 1: Read
Transaction executes on a private copy of all accessed objects
Phase 2: Validate
Check if applying the transaction would break isolation
Phase 3: Write
Write the transaction's updates out to the main database

Generic Transaction

... with snapshots

Read Phase

Collect...

$\textbf{ReadSet}(\mathcal T)$
The set of objects read by $\mathcal T$
$\textbf{WriteSet}(\mathcal T)$
The set of objects written by $\mathcal T$

Validation Phase

Pick a serial order (e.g., the order in which transactions reach the validation phase)

Make sure the transaction's operations follow this order

T1 and T2 read the same object
OK!
T1 reads an object written by T2 (read-write)
Ok. (T1's validation phase started before T2's)
T2 reads an object written by T1 (write-read)
Ok if T1-Write finishes before T2-Read starts.
T1 and T2 write the same object (write-write)
Ok if T1-Write finishes before T2-Write starts.

Which conflicts we need to check for are different depending on how the phases overlap.

Note: The validation and write phases are NOT instantaneous.

Validation (and selecting a serial order) are a critical section: only one transaction at a time.

Allow write phases to proceed concurrently.

Test 1

If T1's write phase ends before T2's read phase starts, the transactions are already serial.

Test 2

If T1's write phase ends before T2's write phase starts, there's a possibility of write-read conflicts. Abort unless: $$WriteSet(T_1) \cap ReadSet(T_2) = \emptyset$$

Test 3

Otherwise write-read and write-write conflicts are possible. Abort unless: $$WriteSet(T_1) \cap ReadSet(T_2) = \emptyset$$ $$WriteSet(T_1) \cap WriteSet(T_2) = \emptyset$$

Locking vs Snapshot Isolation

Locking
Pro: No need to restart failed transactions in general.
Pro: Minimal overhead if few conflicts
Snapshot Isolation
Pro: Minimal overhead if transactions "safe" by default.
Con: Validation doesn't happen until the validation step.

Idea: Check while the transaction is running

(abort immediately if a violation happens)

Timestamp Concurrency Control

(Snapshot Isolation as seen "in practice")

Each object $A$ gets a read timestamp ($RTS(A)$) and a write timestamp ($WTS(A)$)

Each transaction $\mathcal T$ gets a timestamp ($TS(\mathcal T)$).

(note that these can be logical timestamps like sequence numbers)

Basic Idea: Require transactions to follow $TS(\mathcal T)$ as a serial order.

Abort (and restart) transactions that would break this order.

Assign restarted transactions a brand new timestamp for fairness

When $\mathcal T$ reads object $A$...

If $WTS(A) > TS(\mathcal T)$
The value stored is a "later" version (write-read)
ABORT
If $WTS(A) < TS(\mathcal T)$
The value stored is safe to read.
$RTS(A) \leftarrow MAX(RTS(A), TS(\mathcal T))$

When $\mathcal T$ writes object $A$...

If $RTS(A) > TS(\mathcal T)$
Read from an "later" transaction (write-read)
ABORT
If $WTS(A) > TS(\mathcal T)$
Write from a "later" transaction (write-write)
Ignore the write
Otherwise
The value stored is safe to write.
$WTS(A) \leftarrow MAX(WTS(A), TS(\mathcal T))$
TimeT1T2T3
| W(A)
WTS(A) = 3
| W(A)
Ignore!
| W(A)
Ignore!
| W(B)
WTS(B) = 1
W(B)
WTS(B) = 2

A: $T_3 \rightarrow T_2 \rightarrow T_1$

B: $T_1 \rightarrow T_2$

Cycle... but allowed by Timestamp Concurrency Control

Timestamp CC DOES NOT guarantee conflict-serializability

So is it correct?

TimeT1T2T3
| W(A)
| W(A)
| W(A)
| W(B)
W(B)

T3's write to $A$ "hides" T2's

View Serializability

Conflict Equivalence

Two schedules are conflict-equivalent when you can transform one into the other by reordering any pair of operations that...

  • operate on different objects
  • OR, are both reads

View Equivalence

Two schedules are view-equivalent when you can transform one into the other by reordering any pair of operations that...

  • operate on different objects
  • OR, are both reads
  • OR, are both writes to the same object, but ONLY IF the object is later overwritten by another write.

View Serializability

A schedule is view serializable if it is view-equivalent to some serial schedule

Timestamp concurrency control is guaranteed to produce view-serializable schedules.

View Serializability

On the happens-before graph, throw away edges created by "hidden" write-write conflicts.

If the resulting graph is acyclic, the schedule is view serializable

Another problem... recoverability

TimeT1T2
| W(A)
| R(A)
| W(B)
| COMMIT
ABORT

oops...

  • Buffer all writes until a writer commits (but update WTS immediately)
  • Block readers of object $A$ from committing until the all prior writers to $A$ commit.
  • Abort readers of object $A$ of amu prior writer of $A$ aborts.

Observation: Write-Read conflicts are avoidable... the necessary data existed at one point

Idea: Keep old versions of objects.

Each version of an object has...

  • ... the writing transaction's TS as its WTS.
  • ... the highest TS of any transaction to read it as its RTS.

When $\mathcal T$ reads object $A$...

For the latest version $A_i$ with $WTS(A_i) < TS(\mathcal T)$...
Always...
$RTS(A_i) \leftarrow MAX(RTS(A_i), TS(\mathcal T))$

When $\mathcal T$ writes object $A$...

For the latest version $A_i$ with $WTS(A_i) < TS(\mathcal T)$...
If $RTS(A_i) > TS(\mathcal T)$
Unrecoverable read from an "later" transaction (write-read)
ABORT
If $WTS(A) > TS(\mathcal T)$
Write from a "later" transaction (write-write)
Add the write to the versions of $A$
Otherwise
The value stored is safe to write.
Add the write to the versions of $A$