问津集 #19:Separation or Not——乱序数据分流的写放大建模与策略选择

Apache IoTDB 在论文所述实现中维护了两个 MemTable:C_seq 接收顺序数据,C_nonseq 接收乱序数据。这种分流可以让顺序 SSTable 保持更紧凑的时间范围,也能先积累一批乱序点再做 Compaction,通常有利于范围查询。

Separation or Not 讨论了这个设计的另一面:在总内存固定时,分成两个 MemTable 不保证降低写放大。乱序数据很少时,单 MemTable 的传统策略可能只需做很少的合并;C_nonseq 却会在很长一段时间里积累少量乱序点,最终与更多已经落盘的 SSTable 重叠。论文建模估算两种策略的 Write Amplification,再同时选择是否分流以及 C_seq/C_nonseq 的容量比例。1

1 乱序分流的写放大反例

论文把单 MemTable 策略记为 p i _ c \\pi\_c pi_c,它将所有到达数据写入 C_0,写满后与 Level 1 中时间范围重叠的 SSTable 合并。分流策略记为 p i _ s \\pi\_s pi_s:C_seq 写满后可以直接 Flush,C_nonseq 写满才触发与盘上重叠 SSTable 的合并。两种策略使用相同的总内存,即 C_0 的容量等于 C_seqC_nonseq 之和。1

这里的乱序以 Level 1 已经落盘的数据为参照。论文将盘上数据的最大生成时间记为 L A S T ( R ) . t _ g LAST(R).t\_g LAST(R).t_g。新到达数据点 p p p 的生成时间若大于这个边界,说明它仍然可以接在盘上数据之后,被归入顺序路径;若小于这个边界,它本应位于某些已落盘数据之前,只能进入乱序路径。论文对比的 Stream Processing Late Event,则是根据相邻到达数据点的生成时间判断先后,参照物并不是盘上的时间边界。1

例如,当前 L A S T ( R ) . t _ g = 100 LAST(R).t\_g=100 LAST(R).t_g=100,一个生成时间为 105 的数据点虽然比生成时间为 110 的数据点更晚到达,但它仍会被本文归为顺序数据,因为 105 > 100 105>100 105>100。如果生成时间为 110 的数据已经先一步 Flush, L A S T ( R ) . t _ g LAST(R).t\_g LAST(R).t_g 被推进到 110,这个生成时间为 105 的迟到点才会被归为乱序数据。这也解释了为什么 MemTable 容量会影响乱序比例:C_seq 越小,写满和 Flush 越频繁,盘上的时间边界推进得越快,后续迟到数据落在边界之前的概率也越高。1

图 1:左侧的 p i _ c \\pi\_c pi_c 在乱序点较少时仅触发一次局部合并;右侧的 p i _ s \\pi\_s pi_s 等到 C_nonseq 写满后,要合并这一阶段中持续增长的重叠范围。原图来自论文 Figure 2。1

Figure 2 给出了整篇论文最重要的反例。 p i _ c \\pi\_c pi_c 中的少量乱序点还没有让多个 Flush 批次反复重写相同时间段,Write Amplification 接近 1。 p i _ s \\pi\_s pi_s 把乱序点更早放入 C_nonseq,等它写满时,大量顺序数据已经通过 C_seq 刷成 SSTable。这次 Compaction 需要重写所有重叠文件,分流的 Write Amplification 反而更高。1

论文作者将这项工作明确定位为 Industrial Paper。它没有再设计一种乱序数据结构,主要贡献是估算 p i _ c \\pi\_c pi_c 和 p i _ s \\pi\_s pi_s 的写放大,找到 p i _ s \\pi\_s pi_s 下的容量分配,再用 Delay Analyzer 根据负载变化切换策略。1

2 后继数据点模型与分流策略选择

2.1 单 MemTable 的写放大估算

建模先引入 Subsequent Data Point。假设盘上已经有生成时间为 101~104、106~110 的数据,生成时间为 105 的点此时才到达内存。盘上的 106~110 都生成在这个迟到点之后,论文将它们称为后继数据点。Compaction 写入 105 时,需要同时重写包含这些后继数据点的 SSTable。1

z e t a ( n ) \\zeta(n) zeta(n) 估算 C_0 中积累 n n n 个数据点后,盘上预计有多少后继数据点需要随本次 Compaction 重写。延迟的 PDF f ( x ) f(x) f(x) 和 CDF F ( x ) F(x) F(x) 描述一个数据点可能迟到多久,生成间隔 D e l t a t \\Delta t Deltat 则把这段延迟换算成期间可能已经生成并落盘的数据点数量。三者共同决定 z e t a ( n ) \\zeta(n) zeta(n)。对容量为 n n n 的 C_0,传统策略的预期写放大为:

r _ c = f r a c z e t a ( n ) n + 1 r\_c=\\frac{\\zeta(n)}{n}+1 r_c=fraczeta(n)n+1

z e t a ( n ) / n \\zeta(n)/n zeta(n)/n 对应被 Compaction 重写的盘上数据,加一对应用户本来要写入的这一批数据。论文的计算以数据点为单位,而真实 Compaction 以 SSTable 为单位。因此模型会偏低,论文给出的误差上界是 1 个 Write Amplification 单位。1

这一模型依赖三个近似:各数据点的延迟独立同分布,延迟可由给定的 f ( x ) f(x) f(x) 和 F ( x ) F(x) F(x) 描述,数据按固定的 D e l t a t \\Delta t Deltat 生成。它们让 Delay Analyzer 能在线估算,也构成了模型最明确的证据边界。1

2.2 C_seq/C_nonseq 容量比例与 U 形曲线

对分流策略,论文将两次 C_nonseq 合并之间的过程定义为一个 Phase。一个 Phase 中,C_seq 可以写满并 Flush 多次,C_nonseq 只在 Phase 结束时写满和合并一次。1

图 2:C_seq 在一个 Phase 中多次 Flush,C_nonseq 写满后与当前 Phase 及更早阶段生成的重叠 SSTable 合并。原图来自论文 Figure 6。1

设总内存容量为 n n n,C_seq 容量为 n _ m a t h r m s e q n\{\\mathrm{seq}} n_mathrmseq,C_nonseq 容量为 n − n _ m a t h r m s e q n-n\{\\mathrm{seq}} n−n_mathrmseq。 g ( n _ m a t h r m s e q ) g(n\{\\mathrm{seq}}) g(n_mathrmseq) 表示每到达 n _ m a t h r m s e q n\{\\mathrm{seq}} n_mathrmseq 个顺序点时,预期同时到达的乱序点数量。一个 Phase 等到 C_nonseq 写满时,系统共接收:

N _ m a t h r m a r r i v e ( n _ m a t h r m s e q ) = f r a c n _ m a t h r m s e q ( n − n _ m a t h r m s e q ) g ( n _ m a t h r m s e q ) + n − n _ m a t h r m s e q N\{\\mathrm{arrive}}(n\{\\mathrm{seq}}) =\\frac{n\{\\mathrm{seq}}(n-n\{\\mathrm{seq}})}{g(n\{\\mathrm{seq}})}+n-n\{\\mathrm{seq}} N_mathrmarrive(n_mathrmseq)=fracn_mathrmseq(n−n_mathrmseq)g(n_mathrmseq)+n−n_mathrmseq

等号右侧的第一项是这一 Phase 期间到达的顺序数据,第二项是刚好填满 C_nonseq 的乱序数据。写放大 r _ s r\s r_s 再把需要重写的数据分成两部分: N _ m a t h r m c u r N\{\\mathrm{cur}} N_mathrmcur 来自当前 Phase 已经 Flush 的 SSTable, N _ m a t h r m b e f N\_{\\mathrm{bef}} N_mathrmbef 来自更早的 Phase。完整比例为:

r _ s ( n _ m a t h r m s e q ) = f r a c N _ m a t h r m c u r + N _ m a t h r m b e f + N _ m a t h r m a r r i v e ( n _ m a t h r m s e q ) N _ m a t h r m a r r i v e ( n _ m a t h r m s e q ) r\s(n\{\\mathrm{seq}})= \\frac{N\{\\mathrm{cur}}+N\{\\mathrm{bef}}+N\{\\mathrm{arrive}}(n\{\\mathrm{seq}})} {N\{\\mathrm{arrive}}(n\{\\mathrm{seq}})} r_s(n_mathrmseq)=fracN_mathrmcur+N_mathrmbef+N_mathrmarrive(n_mathrmseq)N_mathrmarrive(n_mathrmseq)

这个模型中的容量选择存在两个相反方向。C_nonseq 过小,它会频繁写满,触发更多乱序 Compaction。C_seq 过小,顺序数据频繁 Flush,盘上的最大生成时间更快向前推进,又会让更多新到达数据被判定为乱序。两个效应叠加后, r _ s ( n _ m a t h r m s e q ) r\s(n\{\\mathrm{seq}}) r_s(n_mathrmseq) 会出现 U 形,默认对半分内存没有必然的最优性。1

2.3 Delay Analyzer 的策略决策

Separation Policy Tuning Algorithm 先根据 f ( x ) f(x) f(x)、 F ( x ) F(x) F(x) 与 D e l t a t \\Delta t Deltat 计算 r _ c r\_c r_c,然后枚举 KaTeX parse error: Undefined control sequence: \ at position 23: ...athrm{seq}}\\\\in\\̲\[̲1,n-1\\,找到最小的 r _ s ( n _ m a t h r m s e q ) r\s(n\{\\mathrm{seq}}) r_s(n_mathrmseq)。当该最小值低于 r _ c r\_c r_c 时,系统选择分流,并将 C_seq 设为对应容量;否则选择单 MemTable。1

论文在 Apache IoTDB 原型中增加 Delay Analyzer,采集数据的延迟分布。检测到分布变化时,它重新运行上述算法并切换策略。论文将输出称为近似最优,原因也很直接:延迟分布和固定生成间隔都是近似,估算按数据点计数,实际合并还要扩大到完整 SSTable。1

3 实验配置、策略效果与证据边界

3.1 合成数据的模型准确性与动态切换

论文先生成 12 组合成数据,每组 1000 万个数据点。生成时间使用固定间隔 D e l t a t i n 10 , 50 \\Delta t\\in{10,50} Deltatin10,50 的等差数列,延迟使用对数正态分布,参数组合为 m u i n 4 , 5 \\mu\\in{4,5} muin4,5、 s i g m a i n 1.5 , 1.75 , 2 \\sigma\\in{1.5,1.75,2} sigmain1.5,1.75,2。数据按生成时间与延迟之和得到的到达时间排序写入,总 MemTable 容量为 512 个点。1

图 3:在 D e l t a t = 50 \\Delta t=50 Deltat=50、 m u = 5 \\mu=5 mu=5、 s i g m a = 2 \\sigma=2 sigma=2 的合成数据上, p i _ s \\pi\_s pi_s 的 Write Amplification 随 C_seq 容量呈 U 形;容量靠近两端时都会偏离最优点,C_seq 过大时还会超过 p i _ c \\pi\_c pi_c。原图来自论文 Figure 7。1

12 组数据中,模型曲线与实测值保持了一致的趋势。 D e l t a t \\Delta t Deltat 更短、 m u \\mu mu 或 s i g m a \\sigma sigma 更大时,乱序更强,写放大也更高。单个数据点只要落在某个 SSTable 中,重写就会扩大到整个文件,因此估算值与实测值之间会有小于 1 的绝对误差。1

动态实验共写入 2500 万个数据点,固定 m u = 5 \\mu=5 mu=5 和 D e l t a t = 50 \\Delta t=50 Deltat=50,每 500 万个点将 s i g m a \\sigma sigma 从 2 逐步改为 1.75、1.5、1.25 和 1。固定将内存 1:1 分配的 p i _ s ( f r a c 12 n ) \\pi\s(\\frac{1}{2}n) pi_s(frac12n) 在乱序较重的前半段写放大明显较高, p i _ m a t h r m a d a p t i v e \\pi\{\\mathrm{adaptive}} pi_mathrmadaptive 会随延迟分布切换策略,贴近当前更低的一条曲线。1

图 4: p i _ c \\pi\_c pi_c、固定 1:1 分配的 p i _ s \\pi\_s pi_s 与自适应策略的滑动窗口 Write Amplification。原图来自论文 Figure 10。1

3.2 真实数据与工业场景的反向结果

论文还使用了两组真实数据。S-9 包含移动设备向 Windows PC 传输的 27 个维度、3 万个数据点,按论文定义计算的乱序比例为 7.05%。这组数据量太小,论文把总内存预算降到 8 个点才能触发足够的合并。在这个配置下,分流策略的实测 Write Amplification 低于单 MemTable,模型判断与实验方向一致。1

工业合作方的车辆数据集 H 包含 100 万个数据点,生成间隔为 1 秒。超过三分之一的 Time-series 出现过乱序,但乱序数据点占所有点的比例只有 0.0375%,这些点的平均延迟约为 2.49 秒。设备在网络异常时会本地缓冲数据,系统大约每 50 秒批量重发,因此 Delay 带有明显的周期和强自相关,不满足独立同分布假设。1

图 5:数据集 H 的 Delay 存在强自相关;实测与估算的绝对值有差距,但都判定 p i _ c \\pi\_c pi_c 的 Write Amplification 低于 p i _ s \\pi\_s pi_s。原图来自论文 Figure 16。1

数据集 H 上的结果与 S-9 相反。即使延迟不满足模型假设,Delay Analyzer 仍然判定 p i _ c \\pi\_c pi_c 的写放大更低,实测结果也支持这个选择。这组实验说明近似模型在一个非独立 Delay 负载上判对了策略方向,不能进一步证明它在任意周期性、批量回补或分布突变中都能给出准确数值。1

查询结果也提醒了另一个边界。 p i _ s \\pi\_s pi_s 将顺序数据写成时间范围更窄的 SSTable,在论文的 Recent Data Query 中显著降低了 Read Amplification,但更多较小 SSTable 增加了文件读取与 Seek,平均查询延迟在多组负载下反而升高。Historical Query 命中重叠 SSTable 较多时,分流才更容易体现出延迟优势。写入吞吐在 12 组合成数据中大致都落在 84---93 points/ms,论文没有观察到稳定的策略差异,原因是 Compaction 在后台执行且前台写入没有等待它完成。1

论文没有披露完整的实验硬件与存储配置,512 与 8 个数据点的 MemTable 也主要用于验证曲线和触发足够多的合并。这些结果可以证明模型能区分负载、找到 U 形曲线的低点,不能直接外推为生产尺寸下的绝对性能收益。Delay Analyzer 的采样、分布维护开销、切换频率与策略抖动也没有给出组件级数据。1

4 总结

论文证明了乱序分流不一定降低 Write Amplification。C_nonseq 较长时间无法写满时,它覆盖的时间范围会不断扩大,最终可能在一次 Compaction 中重写更多 SSTable。是否分流以及两个 MemTable 怎么分配容量,都与当时的乱序比例和延迟分布有关。1

我认为这套策略很难直接放进现网。论文的目标函数只有 Write Amplification,这是它和通用生产策略之间最明显的差距。实验中已经出现 Read Amplification 降低、查询延迟反而升高的情况,只优化 Write Amplification 无法判断哪种策略在现网更合适。Query Latency、Compaction 带宽、前台写入受到的干扰和策略切换成本都没有进入决策。

模型本身也依赖对数据生成间隔和 Delay Distribution 的概率建模,并假设不同数据点的延迟独立同分布。工业数据集 H 中的批量重发已经产生了明显的周期和相关性。现网还会遇到设备差异、网络状态变化、批量补传和历史数据回灌,负载偏离模型是常态。偏离发生后,Delay Analyzer 也许仍能选对策略方向,但究竟减少了多少 Write Amplification、收益能否覆盖切换和 Compaction 成本,很难提前量化。

目标函数不完整,负载偏离模型后又很难量化优化效果。两者叠在一起,Delay Analyzer 很难直接进入生产系统的核心写入路径。

5 参考资料

1 Separation or Not: On Handing Out-of-Order Time-Series Data in Leveled LSM-Tree

相关推荐
TDengine (老段)1 天前
TDengine 错误处理 — 错误码、异常传播、故障恢复
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
涛思数据(TDengine)1 天前
从“改了就改了“到“每一次变更都可追溯“:工业 AI 实战直播(十二期)
大数据·数据库·人工智能·时序数据库·tdengine
TDengine (老段)1 天前
TDengine 线程模型 — 网络、调度、执行
大数据·数据库·物联网·制造·时序数据库·tdengine·涛思数据
Carl_.Net软开3 天前
NSSM后台启动influx时序数据库
数据库·时序数据库
TDengine (老段)3 天前
TDengine 内存管理 — 池化、缓存、回收
大数据·数据库·物联网·缓存·时序数据库·tdengine·涛思数据
DolphinDB4 天前
一个人抵一个团队的时代,企业级 AI Agent 还需要做什么?
时序数据库·dolphindb
Patrick在香港4 天前
Python 拉取 C&SD 官方 API:香港 2022 年已跨过“超老龄线“,而抚养比正在爬回 1961
android·c语言·python·数据分析·时序数据库·数据可视化·香港
zxsz_com_cn6 天前
预测性维护中的时序数据存储方案:TDengine vs InfluxDB
时序数据库·工业4.0·influxdb·tdengine·预测性维护
TDengine (老段)6 天前
TDengine vs InfluxDB — 全方位对比
大数据·数据库·物联网·时序数据库·tdengine·涛思数据·iotdb