NDB 复制在 NDB 8.3 中支持使用通用 MySQL 服务器多线程应用程序机制(MTA),该机制允许独立的二进制日志事务在副本上并行应用,提高峰值复制吞吐量。
要求
MySQL 服务器 MTA 实现将独立的二进制日志事务委托给一个 worker 线程池(其大小可配置),并协调 worker 线程以确保事务依赖关系被尊重,并在需要时维护提交顺序(见 第 19.2.3 节,“复制线程”)。要使用该功能与 NDB 集群,必须满足以下三个条件:
-
二进制日志事务依赖关系在源上确定。
为实现此目的,源上的
binlog_transaction_dependency_tracking
服务器系统变量必须设置为WRITESET
。(默认值为COMMIT_ORDER
。)在
NDB
中的写集维护工作由 MySQL 二进制日志 injector 线程作为准备和提交每个 epoch 事务到二进制日志的一部分执行。这需要额外的资源,并可能减少峰值吞吐量。 -
事务依赖关系被编码到二进制日志中。
使用
--ndb-log-transaction-dependency=ON
启动 mysqld,以便将NDB
事务依赖关系写入二进制日志。 -
副本配置使用多个 worker 线程。
设置
replica_parallel_workers
以控制副本上的 worker 线程数。默认值为 4。
MTA 配置:源
源 mysqld 配置对于 NDB
MTA 必须包括以下明确设置:
-
binlog_transaction_dependency_tracking
必须设置为WRITESET
。 -
复制源 mysqld 必须使用
--ndb-log-transaction-dependency=ON
启动。
如果设置,replica_parallel_type
必须是 LOGICAL_CLOCK
(默认值)。
NDB
不支持 replica_parallel_type=DATABASE
。
此外,建议您将用于跟踪二进制日志事务写入集的内存量设置为
,其中 E
* P
E
是平均 epoch 大小(作为每个 epoch 的操作数),P
是最大预期并行度。请参阅 写入集跟踪内存使用情况,以获取更多信息。
MTA 配置:副本
副本 mysqld 配置对于 NDB MTA 需要 replica_parallel_workers
大于 1。启用 MTA 时的推荐起始值为 4,这也是默认值。
此外,replica_preserve_commit_order
必须为 ON
。这也是默认值。
事务依赖关系和写入集处理
事务依赖关系是通过分析每个事务的写入集(即事务写入的行集)来检测的。两个事务修改同一行时,它们被认为是依赖的,必须按顺序(即串行)应用以避免死锁或不正确的结果。在表具有次要唯一键的情况下,这些值也将添加到事务的写入集,以检测事务依赖关系。无法确定依赖关系时,mysqld 将回退到考虑事务依赖关系以确保安全。
事务依赖关系在二进制日志中由源 mysqld 编码。依赖关系使用“逻辑时钟”方案编码在 ANONYMOUS_GTID
事件中。(请参阅 第 19.1.4.1 节,“复制模式概念”。)
MySQL(和 NDB Cluster)使用基于哈希冲突检测的写入集实现,基于相关表和索引值的 64 位行哈希。这可靠地检测到相同键的出现,但也可能产生假阳性结果,如果不同的表和索引值哈希到同一个 64 位值;这可能会导致人工依赖关系,从而减少可用的并行度。
事务依赖关系可以通过以下任何一种方式强制:
-
DDL 语句
-
二进制日志旋转或遇到二进制日志文件边界
-
写入集历史记录大小限制
-
写入引用父外键的目标表
更具体地说,执行插入、更新和删除外键 父 表的事务将相对于所有前一个和后续事务进行序列化,而不仅仅是与涉及约束关系的表相关的事务。相反,执行插入、更新和删除外键 子 表(引用)的事务不会与其他事务相互序列化。
MySQL MTA 实现尝试并行应用独立的二进制日志事务。NDB
记录了在一个 epoch 中发生的所有用户事务的所有更改(TimeBetweenEpochs
,默认为 100 毫秒),在一个二进制日志事务中,称为 epoch 事务。因此,为了使两个连续的 epoch 事务独立并行应用,需要确保没有行在两个 epoch 中被修改。如果任何单行在两个 epoch 中被修改,那么它们是依赖的,并将按顺序应用,从而限制了可用的并行度。
Epoch 事务根据源集群在 epoch 中修改的行集来确定独立性,但不包括生成的 mysql.ndb_apply_status
WRITE_ROW
事件,这些事件传递 epoch 元数据。这避免了每个 epoch 事务都依赖于前一个 epoch,但需要在副本上以保持提交顺序应用二进制日志。这也意味着使用 NDB 二进制日志的写入集依赖关系的副本数据库不适合使用不同的 MySQL 存储引擎。
可能需要修改应用程序事务行为,以避免在短时间内对同一行重复修改的模式,以增加可exploitable的应用并行性。
写集跟踪内存使用
使用binlog_transaction_dependency_history_size
服务器系统变量可以设置用于跟踪二进制日志事务写集的内存量,默认为25000行哈希。
如果平均二进制日志事务修改N
行,那么要确定独立(可并行)的事务,直到并行级别P
,我们需要binlog_transaction_dependency_history_size
至少为
。(最大为1000000。)N
* P
有限的历史记录大小导致有限的最大依赖长度可以可靠地确定,从而表达有限的并行性。任何未在历史记录中找到的行可能依赖于最后一个从历史记录中清除的事务。
写集历史记录不像滑动窗口覆盖最后N
个事务;相反,它是一个有限的缓冲区,允许完全填充,然后在完全填充时将其内容完全清除。这意味着历史记录大小随时间呈锯齿形变化,最大可检测到的依赖长度也随时间呈锯齿形变化,因此独立的事务可能仍被标记为依赖项,如果写集历史记录缓冲区在它们被处理之间被重置。
在这个方案中,每个事务在二进制日志文件中都带有一个sequence_number
(1、2、3、...),以及它所依赖的最新二进制日志事务的序列号,我们称之为last_committed
。
在给定的二进制日志文件中,第一个事务具有sequence_number
1和last_committed
0。
如果一个二进制日志事务依赖于其直接前身,那么它的应用程序将被序列化。如果依赖项是早期的事务,那么可能可以并行应用该事务与前一个独立事务。
可以使用mysqlbinlog查看ANONYMOUS_GTID
事件的内容,包括sequence_number
和last_committed
(因此事务依赖项)。
在源上生成的ANONYMOUS_GTID
事件与压缩的事务payload分开处理,包括批量BEGIN
、TABLE_MAP*
、WRITE_ROW*
、UPDATE_ROW*
、DELETE_ROW*
和COMMIT
事件,允许在副本上确定依赖项之前对独立事务进行自动并行解压缩。
已知限制
次要唯一列。具有次要唯一键(即primary key以外的唯一键)的表将所有列发送到源,以便检测唯一键相关的冲突。
如果当前二进制日志模式不包括所有列,而是仅包括更改的列(--ndb-log-updated-only=OFF
、--ndb-log-update-minimal=ON
、--ndb-log-update-as-write=OFF
),这可能会增加从数据节点到SQL节点的数据量。
影响取决于表中的行修改(更新或删除)速率和未修改的列中的数据量。
将NDB复制到InnoDB。 NDB
二进制日志injector事务依赖项跟踪故意忽略了生成的mysql.ndb_apply_status
元数据事件,这些事件作为epoch事务的一部分在副本应用程序上单独处理。对于复制到InnoDB
,没有特殊处理;这可能会导致使用InnoDB
多线程应用程序来消费NDB
MTA二进制日志时出现性能下降或其他问题。