Dependency adapter for LWM (Low Water Mark) based scheduling.
Translates transaction sequence numbers into scheduler task IDs for LWM-based execution.
Concepts
This adapter handles key indexing differences between source and replica:
- seq_num: Originates from the source (e.g., primary server) and starts at 1 for the first transaction in each group. It resets to 1 at group boundaries (e.g., server restarts, configuration changes, or failures). seq_num=0 is reserved for invalid cases (e.g., SEQ_UNINIT).
- task_id: Assigned sequentially by the scheduler on the replica, starting from 0 and monotonically increasing as long as the applier is running. Unlike seq_num, task_id does not reset and continues growing across seq_num resets. Wrap-around handling: If task_id wraps (uint64_t overflow), the adapter detects it (new < max && max near UINT64_MAX), clears mappings, resets barrier to 0, and continues from the new baseline to maintain order.
- LWM (Low Water Mark): Tracks the longest prefix of tasks executed in commit order (with replica_preserve_commit_order=1). It is computed on the replica based on task_ids.
- LWM = 0: No tasks have executed.
- LWM = 1: Exactly one task has executed (task_id = 0).
- If tasks with task_id 0 through 3 have executed in order, LWM = 4. LWM advances as tasks commit, representing the next task_id that can proceed without violating order.
In CSA, transactions include a sequence number (seq_num) and last_committed (commit_parent), which is the seq_num of the transaction that must commit before this one, or 0 if the dependency is unknown (e.g., SEQ_UNINIT or first transaction in a group).
This adapter maintains a mapping from seq_num to task_id. The solve() function computes the clock delay for a given task, which is the target LWM value at which the task can execute. This delay represents the dependency relative to the initial LWM (equivalent to the absolute LWM value required). The task is delayed until the current LWM reaches or exceeds this value.
The delay is computed as the maximum of:
- A barrier value (id_after_barrier), set when commit_parent == 0 to handle unknown dependencies by ensuring the task waits for all prior tasks (including itself if first, but resulting in delay=0 for immediate execution).
- (task_id of commit_parent + 1), if the mapping for commit_parent exists and this value exceeds the barrier.
When commit_parent == 0 (unknown dependency, e.g., SEQ_UNINIT or group start/first transaction), the seq_to_task map is cleared to bound its size, a new barrier is set to the current max_task_id (the task_id of this task), and the delay equals this barrier. For the first transaction (task_id=0), barrier=0 and delay=0, allowing immediate execution. For subsequent tasks with unknown dependency, it sets a barrier to wait for prior tasks. This ensures ordering across group boundaries without retaining stale mappings.
Example
Delays indicate the LWM value required for execution. Assume seq_num starts at 1, with commit_parent=0 for the first transaction (unknown dependency, immediate execution):
- Transaction task_id=0, seq_num=1, commit_parent=0 → delay=0 (unknown dependency; execute immediately as first task)
- Transaction task_id=1, seq_num=2, commit_parent=1 → delay=1 (wait for LWM >=1, i.e., task 0 committed)
- Transaction task_id=2, seq_num=3, commit_parent=1 → delay=1 (depends on task 0)
- Transaction task_id=3, seq_num=4, commit_parent=2 → delay=2 (wait for LWM >=2, i.e., task 1 committed)
- Transaction task_id=4, seq_num=5, commit_parent=0 → delay=4 (unknown dependency; barrier set to wait for all prior tasks)
- Transaction task_id=5, seq_num=6, commit_parent=3 → delay=4 (wait for LWM >=4, i.e., task 3 committed; respects barrier)
Group boundary (seq_num resets to 1; map cleared, new barrier set):
- Transaction task_id=6, seq_num=1, commit_parent=0 → delay=6 (unknown dependency; map cleared, barrier=6; wait for LWM >=6)
- Transaction task_id=7, seq_num=2, commit_parent=1 → delay=7 (depends on task_id=6 (seq_num=1); wait for LWM >=7)