为什么你的同步 SQL 提交要等待 VPN 往返
可用性组中的同步提交包含远程日志的固化。在归咎于带宽或切换模式之前,先测量那条路径。
在一份副本迁到第二站点之后,写入延迟突然升高,而隧道是最新的组件,于是它背了锅。隧道可能是无辜的:在同步提交的可用性组内,主副本必须等到远程日志写入完成才能确认提交,所以无论链路能承载多少兆比特,每个事务都要付一次往返。吞吐量衡量每秒字节数,延迟衡量一次交换要多久,而一次提交只需要一次小交换就能完成。一个数字就能终结争论:小事务的提交时长,先在可用性数据库里测,再对同一实例上的本地表测一次。
主副本究竟在等什么
在同步提交下,主副本会一直扣住提交确认,直到伙伴确认其日志记录已经固化。固化是指持久写入辅助副本的事务日志,而不是重放到它的数据页。redo 可以落后几分钟,却不会给主副本的提交增加一毫秒。
Microsoft 关于可用性模式的文档 讲解了这个握手,并描述了伙伴掉线时平台会怎么做。标记为同步的副本是一条关于等待的规则,而不是对每次提交都存在健康远程副本的永久承诺。断开路径是单独定义的,它并不只是提交失败这么简单。
所以你看到的等待是一个总和:日志传输、两台主机上的调度,以及远程存储写入。日志卷饱和的辅助副本会加上任何速度测试都显示不出来的时间。当投诉点名 VPN 时,那块磁盘很少被测到。
带宽不是往返延迟
速度测试会短暂灌满路径并报告速率。提交问的是更窄的东西:一条小消息到达对端、被持久化并返回需要多久。路径长度、排队和丢包决定这个数字,而供应商抬高上限时它们一个都不会改善。
因此,一条「快」隧道会给由大量小型顺序事务构成的写密集型负载加上可见的惩罚。同样的总字节数分成一千次提交,就要付一千次往返;一次批量传输只付一次。
请网络所有者测量代表性负载下副本路由本身的延迟和丢包。从办公笔记本 ping VPN 网关,或对附近节点做速度测试,几乎说明不了日志真正走的那条路径。让测量窗口与写入变慢的时段重叠。
改动之前先读副本状态
从实例对自己的报告开始,因为双方对伙伴关系是否健康的看法可能不一致,而且 仪表盘平均值会掩盖哪条队列在移动。下面的查询回答不同的问题:一个读取按数据库的复制状态,另一个读取每个副本的配置。动任何模式之前,两个都要跑。
SELECT ag.name AS ag_name, drs.database_id, drs.synchronization_state_desc,
drs.log_send_queue_size, drs.redo_queue_size, drs.secondary_lag_seconds
FROM sys.dm_hadr_database_replica_states AS drs
JOIN sys.availability_groups AS ag ON ag.group_id = drs.group_id;
SELECT ar.replica_server_name, ar.availability_mode_desc, ar.failover_mode_desc
FROM sys.availability_replicas AS ar;
第一个结果把日志传输和 redo 分开。log_send_queue_size 上升而 redo_queue_size 持平,说明日志还没到;反过来则说明日志到了而重放落后。只有第一种情况在提交路径上。
第二个结果确认哪些副本真的按你假设的方式配置,因为早前事件中改过的模式往往会保持改动且没有记录。读你关心的那一行,而不是组默认值:availability_mode_desc 和 failover_mode_desc 是按副本的。secondary_lag_seconds 只存在于 SQL Server 2016 及更高版本,所以较旧的版本只会留下发送队列和 redo 两列。
然后测量一次有代表性的写入,而不是相信仪表盘平均值。在可用性数据库里运行下面的代码块,再在同一实例上组外数据库里对同样的表结构运行一次。两个时长之差就是远程日志的粗略代价,在与网络团队争论时它比图表站得住得多。之后每次只改一个变量,否则这个差值就没有意义了。
IF OBJECT_ID('dbo.CommitProbe') IS NULL
CREATE TABLE dbo.CommitProbe (probe_id int PRIMARY KEY, touched_at datetime2(3));
IF NOT EXISTS (SELECT 1 FROM dbo.CommitProbe WHERE probe_id = 1)
INSERT INTO dbo.CommitProbe (probe_id) VALUES (1);
SET STATISTICS TIME ON;
BEGIN TRANSACTION;
UPDATE dbo.CommitProbe SET touched_at = SYSUTCDATETIME() WHERE probe_id = 1;
COMMIT TRANSACTION;
SET STATISTICS TIME OFF;
异步提交买到了什么,又付出了什么
使用异步提交时,主副本不再等待远程固化确认,往返也就离开了提交路径。提交延迟下降,辅助副本可以自由落后到没有人控制的程度。这是对等待的真正修复,也是对承诺的真正改变。
代价是一个关于恢复点的决定。如果主副本在其日志到达你真正可用的故障转移目标之前就死了,把服务推到异步副本上会丢掉应用已经报告为已提交的事务。没有哪个配置对话框会这样呈现它,因为这是关于可接受损失的业务问题,而不是性能设置。在事件发生前写下可容忍的损失窗口,而不是在事件中。
与数据所有者书面约定模式,并附上首选目标不可用时的故障转移流程。设计文档里的同步不是操作手册,异步也不是停止盯队列的许可证。
选择一个事后能为之辩护的修复
有四个诚实的选项。在应用语义允许批处理的地方砍掉可避免的提交往返;修复你测到的传输或存储瓶颈,这可能意味着辅助副本上更快的日志卷,而不是更大的管道;把副本挪近以缩短往返;或者放宽保护策略、接受更宽的损失窗口。每个选项都用别的东西换延迟,第四个换的是持久性。选择之前先说出这笔花费。
在工作负载的延迟目标与持久性要求同时摆在面前时做决定,因为优化一个会悄悄花掉另一个。然后重新运行改动前测过的同一个事务。无法移动那个数字的修复,就是一张戴着变更单的猜测。
把改动前后的读数留在变更记录里。否则下一个看到慢写入的人会从头重做调查,并再次开始怪隧道。