MySQL 26.7.0
Source Code Documentation
dependency_adapter_lwm.h
Go to the documentation of this file.
1// Copyright (c) 2026, Oracle and/or its affiliates.
2//
3// This program is free software; you can redistribute it and/or modify
4// it under the terms of the GNU General Public License, version 2.0,
5// as published by the Free Software Foundation.
6//
7// This program is distributed to work with certain software (including
8// but not limited to OpenSSL) that is licensed under separate terms,
9// as designated in a particular file or component or in included license
10// documentation. The authors of MySQL hereby grant you an additional
11// permission to link the program and your derivative works with the
12// separately licensed software that they have either included with
13// the program or referenced in the documentation.
14//
15// This program is distributed in the hope that it will be useful,
16// but WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18// GNU General Public License, version 2.0, for more details.
19//
20// You should have received a copy of the GNU General Public License
21// along with this program; if not, write to the Free Software
22// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23
24#ifndef MYSQL_CSA_DEPENDENCY_ADAPTER_LWM_H
25#define MYSQL_CSA_DEPENDENCY_ADAPTER_LWM_H
26
27#include <ankerl/unordered_dense.h>
28#include <algorithm>
29#include <limits>
31
32namespace mysql::csa {
33
34/// @brief Dependency adapter for LWM (Low Water Mark) based scheduling.
35/// Translates transaction sequence numbers into scheduler task IDs for
36/// LWM-based execution.
37///
38/// @section Indexing Concepts
39///
40/// This adapter handles key indexing differences between source and replica:
41/// - seq_num: Originates from the source (e.g., primary server) and starts at 1
42/// for the first transaction in each group. It resets to 1 at group
43/// boundaries (e.g., server restarts, configuration changes, or failures).
44/// seq_num=0 is reserved for invalid cases (e.g., SEQ_UNINIT).
45/// - task_id: Assigned sequentially by the scheduler on the replica, starting
46/// from 0 and monotonically increasing as long as the applier is running.
47/// Unlike seq_num, task_id does not reset and continues growing across
48/// seq_num resets. Wrap-around handling: If task_id wraps (uint64_t
49/// overflow), the adapter detects it (new < max && max near UINT64_MAX),
50/// clears mappings, resets barrier to 0, and continues from the new baseline
51/// to maintain order.
52/// - LWM (Low Water Mark): Tracks the longest prefix of tasks executed in
53/// commit order (with replica_preserve_commit_order=1). It is computed on
54/// the replica based on task_ids.
55/// - LWM = 0: No tasks have executed.
56/// - LWM = 1: Exactly one task has executed (task_id = 0).
57/// - If tasks with task_id 0 through 3 have executed in order, LWM = 4.
58/// LWM advances as tasks commit, representing the next task_id that can
59/// proceed without violating order.
60///
61/// In CSA, transactions include a sequence number (seq_num) and last_committed
62/// (commit_parent), which is the seq_num of the transaction that must commit
63/// before this one, or 0 if the dependency is unknown (e.g., SEQ_UNINIT or
64/// first transaction in a group).
65///
66/// This adapter maintains a mapping from seq_num to task_id. The solve()
67/// function computes the clock delay for a given task, which is the target
68/// LWM value at which the task can execute. This delay represents the
69/// dependency relative to the initial LWM (equivalent to the absolute LWM
70/// value required). The task is delayed until the current LWM reaches or
71/// exceeds this value.
72///
73/// The delay is computed as the maximum of:
74/// - A barrier value (id_after_barrier), set when commit_parent == 0 to handle
75/// unknown dependencies by ensuring the task waits for all prior tasks
76/// (including itself if first, but resulting in delay=0 for immediate
77/// execution).
78/// - (task_id of commit_parent + 1), if the mapping for commit_parent exists
79/// and this value exceeds the barrier.
80///
81/// When commit_parent == 0 (unknown dependency, e.g., SEQ_UNINIT or group
82/// start/first transaction), the seq_to_task map is cleared to bound its size,
83/// a new barrier is set to the current max_task_id (the task_id of this task),
84/// and the delay equals this barrier. For the first transaction (task_id=0),
85/// barrier=0 and delay=0, allowing immediate execution. For subsequent tasks
86/// with unknown dependency, it sets a barrier to wait for prior tasks. This
87/// ensures ordering across group boundaries without retaining stale mappings.
88///
89/// @section Example
90/// Delays indicate the LWM value required for execution. Assume seq_num starts
91/// at 1, with commit_parent=0 for the first transaction (unknown dependency,
92/// immediate execution):
93/// - Transaction task_id=0, seq_num=1, commit_parent=0 → delay=0 (unknown
94/// dependency; execute immediately as first task)
95/// - Transaction task_id=1, seq_num=2, commit_parent=1 → delay=1 (wait for LWM
96/// >=1, i.e., task 0 committed)
97/// - Transaction task_id=2, seq_num=3, commit_parent=1 → delay=1 (depends on
98/// task 0)
99/// - Transaction task_id=3, seq_num=4, commit_parent=2 → delay=2 (wait for LWM
100/// >=2, i.e., task 1 committed)
101/// - Transaction task_id=4, seq_num=5, commit_parent=0 → delay=4 (unknown
102/// dependency; barrier set to wait for all prior tasks)
103/// - Transaction task_id=5, seq_num=6, commit_parent=3 → delay=4 (wait for LWM
104/// >=4, i.e., task 3 committed; respects barrier)
105///
106/// Group boundary (seq_num resets to 1; map cleared, new barrier set):
107/// - Transaction task_id=6, seq_num=1, commit_parent=0 → delay=6 (unknown
108/// dependency; map cleared, barrier=6; wait for LWM >=6)
109/// - Transaction task_id=7, seq_num=2, commit_parent=1 → delay=7 (depends on
110/// task_id=6 (seq_num=1); wait for LWM >=7)
112 public:
117
118 inline std::pair<Clock_delay, Task_id_resolved> solve(
119 Task_id task_id, int64_t seq_num, int64_t commit_parent) override {
120 const uint64_t current_task_id = task_id.get();
121 const bool is_contiguous = (m_prev_seq + 1) == seq_num;
122
123 // Detect wrap-around: if new task_id < max_task_id and max_task_id is near
124 // overflow
125 if (current_task_id < max_task_id &&
127 // Assume wrap-around occurred; reset state to new epoch
128 seq_to_task.clear();
130 m_dense_mapping = false;
131 max_task_id = current_task_id;
132 } else {
133 max_task_id = current_task_id;
134 }
135
136 // Translate commit_parent to task ID if valid
137 uint64_t delay = id_after_barrier;
138 if (commit_parent > 0 && is_contiguous) {
139 uint64_t parent_id{0};
140 bool has_parent{false};
141 // Common case fast path: commit_parent equals previous sequence.
142 if (commit_parent == m_prev_seq) {
143 parent_id = m_prev_task_id;
144 has_parent = true;
145 } else if (m_dense_mapping && commit_parent >= m_dense_base_seq &&
146 commit_parent <= m_prev_seq) {
147 parent_id = m_dense_base_task_id +
148 static_cast<uint64_t>(commit_parent - m_dense_base_seq);
149 has_parent = true;
150 } else {
151 auto it = seq_to_task.find(commit_parent);
152 if (it != seq_to_task.end()) {
153 parent_id = it->second;
154 has_parent = true;
155 }
156 }
157 if (has_parent) {
158 uint64_t next_parent =
159 (parent_id == std::numeric_limits<uint64_t>::max()) ? 0
160 : parent_id + 1;
161 if (next_parent > id_after_barrier ||
162 (next_parent < parent_id &&
164 delay = next_parent;
165 }
166 }
167 // If not found, assume cleared, use id_after_barrier
168 } else if (commit_parent == 0 || seq_num <= m_prev_seq ||
169 (m_prev_seq + 1) < seq_num || seq_num <= 0) {
170 seq_to_task.clear();
172 delay = id_after_barrier;
173 m_dense_mapping = (seq_num > 0);
174 m_dense_base_seq = seq_num;
175 m_dense_base_task_id = current_task_id;
176 } else {
177 // Irregular sequence progression - fall back to explicit mapping.
178 m_dense_mapping = false;
179 }
180 // For other invalid cases, use current id_after_barrier
181
182 // In dense contiguous region, seq->task_id mapping is arithmetic and does
183 // not require hash-table updates.
184 if (!m_dense_mapping) {
185 seq_to_task.insert_or_assign(seq_num, current_task_id);
186 }
187 m_prev_seq = seq_num;
188 m_prev_task_id = current_task_id;
189
190 return std::make_pair<Clock_delay, Task_id_resolved>(delay, {});
191 }
192
193 /// Get tracked mapping size for unit tests.
194 /// In dense mode, this is the logical contiguous range size (not hash
195 /// entries), because seq->task mapping is resolved arithmetically.
196 /// @return The number of tracked tasks.
197 std::size_t size() const {
198 if (!m_dense_mapping) {
199 return seq_to_task.size();
200 }
202 return 0;
203 }
204 return static_cast<std::size_t>(m_prev_seq - m_dense_base_seq + 1);
205 }
206
207 private:
208 ankerl::unordered_dense::map<int64_t, uint64_t> seq_to_task;
209 uint64_t max_task_id{1};
210 uint64_t id_after_barrier{0};
211 /// Previous sequence number to check continuity
212 int64_t m_prev_seq{0};
213 /// Previous task id for fast-path parent resolution.
214 uint64_t m_prev_task_id{0};
215 /// True when current seq->task mapping is dense and can be resolved
216 /// arithmetically (without hash inserts/lookups for most operations).
217 bool m_dense_mapping{false};
218 /// First seq/task pair for the current dense mapping range.
221};
222
223} // namespace mysql::csa
224
225#endif // MYSQL_CSA_DEPENDENCY_ADAPTER_LWM_H
Dependency adapter for LWM (Low Water Mark) based scheduling.
Definition: dependency_adapter_lwm.h:111
uint64_t max_task_id
Definition: dependency_adapter_lwm.h:209
int64_t m_prev_seq
Previous sequence number to check continuity.
Definition: dependency_adapter_lwm.h:212
int64_t m_dense_base_seq
First seq/task pair for the current dense mapping range.
Definition: dependency_adapter_lwm.h:219
std::size_t size() const
Get tracked mapping size for unit tests.
Definition: dependency_adapter_lwm.h:197
Dependency_adapter_lwm()
Definition: dependency_adapter_lwm.h:116
bool m_dense_mapping
True when current seq->task mapping is dense and can be resolved arithmetically (without hash inserts...
Definition: dependency_adapter_lwm.h:217
std::pair< Clock_delay, Task_id_resolved > solve(Task_id task_id, int64_t seq_num, int64_t commit_parent) override
Solves dependencies - figures out after which task a task with the given id should run.
Definition: dependency_adapter_lwm.h:118
uint64_t m_dense_base_task_id
Definition: dependency_adapter_lwm.h:220
ankerl::unordered_dense::map< int64_t, uint64_t > seq_to_task
Definition: dependency_adapter_lwm.h:208
uint64_t id_after_barrier
Definition: dependency_adapter_lwm.h:210
uint64_t m_prev_task_id
Previous task id for fast-path parent resolution.
Definition: dependency_adapter_lwm.h:214
Class that resolves dependencies based on transaction sequence number and last committed.
Definition: dependency_adapter.h:41
std::optional< uint64_t > Clock_delay
Optional clock delay value.
Definition: dependency_adapter.h:48
mysql::scheduler::Task_id Task_id
Alias for task identifier from scheduler.
Definition: dependency_adapter.h:44
std::optional< Task_id > Task_id_resolved
Optional resolved task identifier.
Definition: dependency_adapter.h:46
Represents the identifier of a task ingested by the scheduler,.
Definition: task_id.h:41
uint64_t get() const
Definition: task_id.h:61
ValueType max(X &&first)
Definition: gtid.h:103
Definition: channel.cpp:28