Basic Timestamp Protocol
Read Timestamp (RTS)
Read aloud is unavailable in this browser.
Tracking When Data Is Read
In lock-based concurrency control, we prevent conflicts by forcing transactions to wait. But what if we could detect conflicts after they occur and simply abort the offending transaction? This is the fundamental insight behind timestamp-based protocols, and it begins with a deceptively simple question: When was this data item last read?
The Read Timestamp (RTS) is the answer to this question—a metadata field maintained for every data item in the database that records the largest timestamp of any transaction that has successfully read that item. Understanding RTS is essential because it forms half of the conflict detection mechanism that enables timestamp ordering to guarantee serializability without ever blocking a transaction.
By the end of this page, you will understand what read timestamps are, why they're maintained, how they're updated during read operations, and how they enable detection of write-after-read conflicts. You'll see RTS as a critical piece of the timestamp ordering puzzle that we'll assemble across this module.
The Concept of Read Timestamps
Every data item in a timestamp-based system maintains metadata about when it was last accessed. The Read Timestamp (RTS) of a data item X, denoted RTS(X), stores the largest timestamp of any transaction that has successfully read X.
Consider what this means: if RTS(X) = 100, we know that some transaction with timestamp 100 has read X, and no transaction with a timestamp larger than 100 has read X since the current value was written. This seemingly simple piece of information has profound implications for conflict detection.
Remember that each transaction receives a unique timestamp TS(T) when it begins, typically from a system clock or logical counter. This timestamp represents the transaction's position in the serialization order—transactions with smaller timestamps are logically "older" and should appear earlier in any equivalent serial schedule.
Formal Definition:
For any data item X in the database:
RTS(X) = max{TS(Ti) | Ti has successfully read X}
This definition captures the essential property: RTS tracks the "youngest" (highest timestamp) transaction that has read the current value of the data item. When a new transaction T reads X:
- If TS(T) > RTS(X), then RTS(X) is updated to TS(T)
- If TS(T) ≤ RTS(X), then RTS(X) remains unchanged (a younger transaction has already read X)
The read timestamp is initialized when a data item is first written—typically set to the write timestamp of the creating transaction.
| Property | Description | Significance |
|---|---|---|
| Monotonically Increasing | RTS(X) never decreases | Once a young transaction reads, older writes are violations |
| Per-Item Granularity | Each data item has its own RTS | Enables fine-grained conflict detection |
| Updated on Successful Reads | Only successful reads update RTS | Failed/aborted reads don't affect protocol |
| Bounds Future Writes | RTS(X) constrains which transactions can write X | Prevents temporal violations |
Why Track Read Timestamps?
The read timestamp serves a specific and critical purpose: detecting write-after-read conflicts that would violate serializability. To understand why, we must think about what it means for a schedule to be serializable.
In timestamp ordering, we're trying to ensure that the schedule is equivalent to running all transactions in timestamp order—T1 before T2 if TS(T1) < TS(T2). Now consider what happens if:
- Transaction T2 (timestamp 20) reads data item X
- Later, transaction T1 (timestamp 10) tries to write X
In a serial schedule respecting timestamp order, T1 would execute completely before T2 starts. T2 would read the value written by T1. But in our actual execution, T2 has already read X—potentially an old value that T1 hasn't yet updated. This is a temporal violation: T2 has read a value that, in the equivalent serial schedule, wouldn't exist yet.
When an older transaction (smaller timestamp) tries to write a data item that a younger transaction (larger timestamp) has already read, we have a non-serializable situation. The younger transaction has seen a "future" value that will be overwritten by the older transaction's write. RTS(X) lets us detect exactly this scenario.
How RTS Enables Detection:
When transaction T wants to write data item X, the protocol checks:
If TS(T) < RTS(X), then T is trying to write a value that a younger transaction has already read.
This is precisely the write-after-read violation described above. The transaction T must be aborted and restarted with a new (larger) timestamp, giving it a chance to execute without violating the intended serialization order.
Let's trace through a concrete example:
| Time | Transaction | Operation | RTS(X) | Outcome |
|---|---|---|---|---|
| t1 | — | Initial state | 0 | X has never been read |
| t2 | T2 (TS=20) | Read(X) | 20 | RTS(X) updated to 20 |
| t3 | T1 (TS=10) | Write(X) | 20 | TS(T1)=10 < RTS(X)=20 → ABORT T1 |
| t4 | T1' (TS=25) | Write(X) | 20 | TS(T1')=25 > RTS(X)=20 → OK |
In this example, T1's original attempt to write X fails because T2 (a younger transaction) has already read X. When T1 restarts as T1' with a new timestamp of 25, it becomes younger than T2, and the write is allowed—the serialization order is now T2 before T1', which is consistent with T2 reading before T1' writes.
RTS Update Mechanics
Understanding exactly when and how RTS is updated is essential for correctly implementing and reasoning about timestamp protocols. The mechanics are straightforward but have important nuances.
The RTS Update Rule:
When transaction T successfully reads data item X:
if TS(T) > RTS(X) then
RTS(X) := TS(T)
end if
Note the key word successfully. If a read operation is rejected (which, as we'll see, can happen in certain cases), the RTS is not updated. Only reads that actually return data to the transaction affect the timestamp.
- •Condition 1: Read Succeeds — The read operation must complete without being rejected by the protocol
- •Condition 2: Timestamp Comparison — RTS is only updated if TS(T) > RTS(X), ensuring RTS never decreases
- •Condition 3: Atomicity — The comparison and update must be atomic to prevent race conditions
- •Condition 4: Transaction Not Aborted — If T is later aborted, ideally RTS should be rolled back (implementation-dependent)
Implementation Considerations:
In practice, maintaining read timestamps introduces storage overhead—every data item needs an additional field. For tuple-level granularity, this means every row in every table has an RTS field. The update operation must also be atomic, which can create contention on frequently-read items.
Some systems optimize by:
- Lazy RTS Updates: Batching updates to reduce write amplification
- Approximate RTS: Using epoch-based timestamps rather than precise values
- Probabilistic Tracking: Only tracking RTS for "hot" data items
- Hybrid Approaches: Combining RTS with locks for specific access patterns
These optimizations trade precision for performance, but must be carefully designed to preserve correctness.
RTS(X) is monotonically non-decreasing: once RTS(X) = k, it can never become less than k without the data item being rewritten. This invariant is what enables reliable conflict detection—if RTS(X) = 50, we're guaranteed that any transaction with TS < 50 cannot safely write X.
RTS in the Bigger Picture
Read timestamps don't work in isolation—they're one half of a dual-timestamp system. Each data item maintains both:
- RTS(X): The largest timestamp of any transaction that has read X
- WTS(X): The largest timestamp of any transaction that has written X
Together, these timestamps form a complete picture of how a data item has been accessed, enabling the protocol to detect both write-after-read and read-after-write conflicts.
- •Track which transactions have read this item
- •Detect write-after-read conflicts
- •Constraint: T can write X only if TS(T) ≥ RTS(X)
- •Updated by read operations
- •Prevents older writes after younger reads
- •Track which transaction wrote this value
- •Detect read-after-write conflicts
- •Constraint: T can read X only if TS(T) ≥ WTS(X)
- •Updated by write operations
- •Prevents older reads of newer values
The Serialization Principle:
The combination of RTS and WTS enforces a fundamental invariant: operations occur in timestamp order. Specifically:
- If T2 reads X after T1 wrote X, then TS(T2) > TS(T1) (enforced by WTS)
- If T1 writes X after T2 read X, then TS(T1) > TS(T2) (enforced by RTS)
Any violation of these conditions indicates a non-serializable access pattern and triggers an abort. The beauty of this approach is that we never block—we either allow the operation immediately or abort and retry.
This is in stark contrast to locking protocols where transactions wait for locks. Timestamp ordering trades potential blocking for potential aborts, which can be advantageous in systems where most transactions don't conflict and waiting is costly.
RTS Examples and Edge Cases
Let's work through several examples to solidify understanding of how RTS behaves in various scenarios.
Example 1: Sequential Reads by Increasingly Younger Transactions
| Step | Transaction | Operation | RTS(X) Before | RTS(X) After |
|---|---|---|---|---|
| 1 | T10 (TS=10) | Read(X) | 0 | 10 |
| 2 | T25 (TS=25) | Read(X) | 10 | 25 |
| 3 | T30 (TS=30) | Read(X) | 25 | 30 |
| 4 | T50 (TS=50) | Read(X) | 30 | 50 |
In this simple case, each successive read is by a younger transaction, so RTS is updated each time. After all reads, RTS(X) = 50, indicating that no transaction with TS < 50 can safely write X.
Example 2: Out-of-Order Reads
| Step | Transaction | Operation | RTS(X) Before | RTS(X) After | Notes |
|---|---|---|---|---|---|
| 1 | T50 (TS=50) | Read(X) | 0 | 50 | RTS jumps to 50 |
| 2 | T10 (TS=10) | Read(X) | 50 | 50 | TS(T10)=10 < RTS=50, no update |
| 3 | T30 (TS=30) | Read(X) | 50 | 50 | TS(T30)=30 < RTS=50, no update |
| 4 | T60 (TS=60) | Read(X) | 50 | 60 | TS(T60)=60 > RTS=50, update |
This example shows that once a young transaction reads, RTS "locks in" that high value. Older transactions can still read (reads almost always succeed in basic timestamp ordering), but they don't affect RTS.
Example 3: Write Rejection Due to RTS
| Step | Transaction | Operation | Action | Result |
|---|---|---|---|---|
| 1 | T40 (TS=40) | Read(X) | RTS(X) := 40 | Read succeeds |
| 2 | T20 (TS=20) | Write(X, v) | Check: 20 < RTS(X)=40 | ABORT T20 |
| 3 | T20 restarts as T50 | — | New timestamp assigned | — |
| 4 | T50 (TS=50) | Write(X, v) | Check: 50 ≥ RTS(X)=40 | Write succeeds |
If T20 had already performed other operations before being aborted, those might need to be undone. Additionally, if other transactions read T20's uncommitted writes, they too might need to abort. This is the cascading abort problem, which we'll address when discussing strict timestamp protocols.
Example 4: Same-Timestamp Tie-Breaking
What if two transactions have the same timestamp? In practice, true timestamp collisions are prevented by:
- Sufficient Resolution: Nanosecond clocks or logical counters ensure uniqueness
- Transaction ID Tie-Breaking: If TS(T1) = TS(T2), use transaction ID as secondary sort
- Hybrid Timestamps: Combine physical time with logical sequence numbers
For theoretical analysis, we assume all timestamps are unique. In implementation, the system must guarantee this property.
RTS Performance Implications
Maintaining read timestamps has real performance costs that database designers must carefully consider. Unlike write timestamps (which are updated only on writes), RTS is updated on every read—and reads are typically far more common than writes.
- •Storage Overhead: Every tuple requires additional bytes for RTS (typically 8 bytes for a 64-bit timestamp)
- •Write Amplification: Read-only transactions become read-write because they must update RTS
- •Cache Invalidation: RTS updates invalidate cache lines, hurting read performance
- •Contention on Hot Items: Many transactions reading the same item create update contention
- •Logging Overhead: If RTS changes are logged for recovery, read-heavy workloads generate massive logs
The Write Amplification Problem:
Consider a pure read-only analytics query scanning a million rows. In a naive timestamp implementation, this query would:
- Read 1,000,000 tuples
- Compare TS(T) with RTS for each
- Update RTS for many of them (potentially all)
- Write 1,000,000 RTS updates back to storage
What should be an I/O-read-only workload becomes half reads, half writes—drastically increasing latency and system load.
Optimization Strategies:
| Strategy | Approach | Trade-off |
|---|---|---|
| Lazy Updates | Batch RTS updates, apply periodically | Reduced precision, may miss conflicts |
| In-Memory RTS | Keep RTS in memory, not on disk | Requires memory, lost on crash |
| Epoch-Based | Update RTS only when epoch changes | Coarser granularity, more aborts |
| Read-Only TXN Optimization | Skip RTS update for read-only transactions | Complex detection, may require declarations |
| Probabilistic | Update RTS randomly or for samples | Trade correctness for performance (rarely used) |
The write amplification problem is one reason pure timestamp ordering is rarely used in practice. MVCC (Multi-Version Concurrency Control) avoids this by maintaining multiple versions of data, allowing reads to access appropriate versions without updating any timestamps. We'll explore MVCC in depth later in this chapter.
Summary: Read Timestamps
We've explored the first critical component of the Basic Timestamp Protocol. Let's consolidate the key concepts:
- •RTS(X) Definition — The largest timestamp of any transaction that has successfully read data item X
- •Monotonicity — RTS(X) only increases; once high, it never decreases without a new write
- •Write-After-Read Detection — RTS enables detection of conflicts where an older transaction tries to write after a younger transaction has read
- •Update Rule — RTS(X) is updated to TS(T) when T reads X, but only if TS(T) > RTS(X)
- •Paired with WTS — RTS works alongside Write Timestamp (WTS) to form complete conflict detection
- •Performance Considerations — RTS updates can cause write amplification, a key challenge in timestamp-based systems
What's Next:
Now that we understand read timestamps, the next page examines Write Timestamps (WTS)—the complementary mechanism that tracks when data items were last written. Together, RTS and WTS form the foundation upon which the complete timestamp ordering protocol is built.
You now understand the Read Timestamp (RTS) concept—what it is, why it's maintained, how it's updated, and what conflicts it detects. This is the first building block of the Basic Timestamp Protocol. Next, we'll examine its counterpart: the Write Timestamp.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.