Administrator
发布于 2026-06-12 / 0 阅读
0
0

大数据回溯场景下的缓存与SQL体积调优经验总结

前言

最近在做大数据回溯相关工作,这里把其中关于 CID 缓存以及 SQL 体积调优的一些经验做一次整理。

这类场景里,随着数据量和回溯范围变大,CID 存储方式、批量读取方式、SQL 体积控制以及查询执行稳定性,都会逐渐成为影响整体性能的关键因素。实际处理过程中,也会涉及缓存成本、查询开销、批次控制和任务稳定性之间的取舍。

这篇主要从实践角度出发,简单总结一下在大数据回溯场景下,CID 缓存和 SQL 体积调优时比较值得关注的几个方向。

背景

这类回溯任务通常有几个共同特征:

  • 用户先圈出一批 CID
  • 指定一个时间范围
  • 再去多张业务表里做历史数据回溯
  • 最终把结果写入一张临时结果表

这里的 CID,指的是我们公司内部的 Customer ID,本质上就是客户唯一标识。回溯任务里,CID 集合就是整条链路的输入起点。

如果 CID 数量不大,日期范围也不长,事情其实不复杂。

麻烦的是一旦 CID 数量上来,或者回溯区间拉长,整个链路就会同时出现两个放大效应:

  • CID 作为输入集合,本身会变成缓存和传输负担
  • SQL 在拼接 CID 集合、日期范围和多表关联之后,体积和执行代价会快速上升

所以这块调优,不能只盯着“查得慢”,而是要分层看问题到底出在缓存、SQL 解析,还是执行展开。

回头看这类问题,最容易混在一起讨论的,其实就是两层事情:

  • 一层是 CID 怎么缓存,怎么让输入集合存得稳、读得顺
  • 一层是 SQL 怎么控制体积,怎么让单批执行成本更可控

这篇后面的内容也主要围绕这两个方向展开。

一、CID 缓存调优

CID 缓存这件事,重点不是能不能存下。

在大数据回溯场景里,CID 通常是整个任务的输入集合。任务一旦开始执行,后续多个批次都会依赖这批 CID。如果每次执行都重新圈选或重新计算,不仅浪费资源,也会让执行链路变得更重。

所以把 CID 做缓存,本质上是在解决两个问题:

  • 避免重复计算
  • 支撑后续批次化读取

但这里不能只停留在“Redis 内存够不够”这个层面。真正需要关注的是下面这些问题:

  • 单个缓存集合会不会过大
  • 批量写入时会不会造成明显抖动
  • 后续批量读取是不是稳定
  • 过期回收或删除时成本会不会集中放大
  • 高峰期多个任务同时存在时,缓存层会不会成为新的瓶颈

换句话说,CID 缓存的核心不是“先放进去再说”,而是“放进去以后,后续整个任务执行还能不能保持稳定”。

1. 先关注单次存储成本

CID 数量一大,缓存集合本身就会变重。

这个时候真正要观察的,不只是条数,而是单个集合的体积和对应的内存占用。因为缓存里的每条 CID,除了字符串本身,还会带来额外的存储组织成本。数据一旦到了一定规模,单次写入、单次读取、过期删除、迁移同步这些动作的成本都会跟着放大。

所以这里更合理的思路不是直接盯着“能放多少条”,而是先做一轮简单测算,先把单个 CID 在缓存里的真实成本摸出来。

我更推荐直接做一组样本测试:

  1. 随机取100050001000050000 个 CID,分别写入几组测试 key。
  2. MEMORY USAGE key 看每组 key 的实际占用。
  3. 计算平均每个 CID 的真实内存成本。
  4. 再根据单 key 可接受的体积,反推单个缓存集合适合承载多少 CID。

一个简单的估算公式可以先这么看:

平均每个 CID 的真实内存成本 ≈ key 实际占用 / CID 数量

单 key 可接受 CID 数 ≈ 单 key 可接受内存上限 / 平均每个 CID 的真实内存成本

举个例子,假设测下来结果大概是这样:

1000 个 CID -> 90 KB
5000 个 CID -> 430 KB
10000 个 CID -> 860 KB
50000 个 CID -> 4.3 MB

那平均下来,每个 CID 的真实内存成本大概就在 85B ~ 90B 左右。

如果自己预期单个缓存 key 最好控制在 8 MB 以内,那么可以反推:

8 * 1024 * 1024 / 90 ≈ 93206

也就是说,单个 key 比较稳妥的承载量大概就在 9 万 左右。这个结果不一定是最终值,但至少能先把量级算出来。

这一步做完以后,再去讨论缓存集合要不要拆分、任务高峰期会占多少内存,心里就有数了,而不是靠感觉拍阈值。

除了单 key 本身,还要顺手估一下高峰期整体占用。比如:

单任务 CID 缓存约 6 MB
高峰期同时存在 20 个任务
那么单这一部分缓存占用大概就是 120 MB

这个值不一定夸张,但如果再叠加别的缓存、热点业务 key、过期未及时回收的数据,就要开始有意识地看整体水位了。

所以这里建议至少把下面这几个量算出来:

  • 单个任务通常会带来多大规模的 CID 集合
  • 这类集合在缓存中大概会形成多大体积
  • 在同一时间窗口内,大概会有多少任务同时存在

只有把这些量级先摸清楚,后面讨论缓存策略才有意义。

2. 把 big key 风险一起考虑进去

CID 放进 Redis 之后,除了占用多少内存,还要考虑 big key 问题。

这个问题麻烦的地方在于,它不一定会马上报错,很多时候是后面慢慢表现出来:

  • 单次写入时间变长
  • 批量读取抖动变大
  • 过期删除或手工清理时容易出现阻塞感
  • 主从同步、迁移、持久化时放大成本

所以这里不要只问“存不存得下”,还要问“这个 key 会不会已经开始变重”。

如果通过前面的样本测算,发现单个缓存集合已经明显偏大,那就要开始考虑逻辑分片存储。

逻辑分片的思路其实比较直接:

  • 不把整批 CID 全塞进一个 key
  • 而是按顺序切成多个片段
  • 每个片段单独存储
  • 后续消费时再按全局偏移或分片顺序去读取

比如一批 20 万 CID,如果测算下来单个 key 最稳妥只适合承载 5 万,那就可以按下面这种方式做逻辑分片:

cid_task_xxx:0 -> 1 ~ 50000
cid_task_xxx:1 -> 50001 ~ 100000
cid_task_xxx:2 -> 100001 ~ 150000
cid_task_xxx:3 -> 150001 ~ 200000

这样做的好处是比较直接的:

  • 避免单个 key 过大
  • 写入压力被拆散
  • 删除和过期回收更容易控制
  • 后续如果某个片段读取异常,也更容易定位

当然,逻辑分片不是白来的。它的前提是消费链路也要跟着支持分片感知。否则只是把大 key 拆开了,但后面批量读取还是按单 key 思维去做,收益就会打折。

所以这里更合适的理解是:

  • 当单 key 已经开始接近 big key 风险区间时,引入逻辑分片
  • 分片不是目的,稳定的存取链路才是目的

3. 再看批量读取方式

CID 缓存的真正价值,不在于“存进去”,而在于“后面怎么被消费”。

大数据回溯任务一般不会一次性把所有 CID 全部丢给查询引擎,而是会按批次读取、分批执行。这样做的好处很直接:

  • 可以控制单批查询成本
  • 可以降低单次失败代价
  • 可以让任务终止、失败重试、执行监控都更容易做

所以缓存层的设计,最好天然服务于“顺序分批读取”这件事。比起一次性全量取出再切片,更稳的方式通常是从一开始就围绕批量消费来设计访问路径。

这个点很关键。因为很多时候问题不是缓存本身存不下,而是读取方式不够适合后续的批次执行模型。

这里可以顺手做一个很简单的批量读取压测:

  1. 固定一组 CID 集合,比如5 万
  2. 分别按500100020005000 这样的批次去读取。
  3. 观察每次读取耗时,以及整轮消费完成的总耗时。

如果某个批次下出现下面这些现象,就要警惕:

  • 单次读取耗时明显跳升
  • 批次放大后整体耗时并没有明显下降
  • 大批次读取容易和后续 SQL 执行高峰叠在一起

这种时候就说明,批次做大带来的未必是收益,可能只是把压力从“读取次数多”换成了“单次读取更重”。

4. 最后补齐生命周期管理

CID 缓存本质上是任务级临时数据,不是长期数据资产。

既然是临时数据,那就要特别注意生命周期管理。如果缓存放得太久,会增加内存压力;如果回收不及时,高峰期多任务叠加时很容易把缓存层拖重。

所以 CID 缓存更适合按任务生命周期去管理:

  • 任务启动时生成
  • 任务执行过程中反复消费
  • 任务完成或超时后及时回收

这里的经验是,缓存不是放进去就完了,后续清理和回收同样属于调优的一部分。否则很多性能问题并不是在任务执行时爆出来,而是在一段时间后的缓存堆积里慢慢出现。

5. CID 缓存调优里容易忽略的点

做这类缓存调优时,有几个地方比较容易被忽略:

  • 只看总量,不看单次写入和单次读取成本
  • 只想着先缓存起来,没有把后续批量消费方式一起考虑
  • 只关注任务执行阶段,没有把过期回收和任务结束后的清理算进去
  • 只看到单任务的资源占用,没有估算高峰期多个任务同时存在时的叠加压力

所以 CID 缓存调优不是简单做一个“能放进去的方案”,而是要把存储、读取和回收当成一整套链路来看。

二、SQL 体积调优

CID 真正进入查询执行层以后,问题就从“怎么存”变成了“怎么查”。

在回溯场景里,SQL 体积变大的原因通常不止一个,而是几个因素叠加在一起:

  • 一批 CID 需要直接进入查询语句
  • 查询往往覆盖一个日期范围,而不是单天
  • 回溯结果通常不是一张表,而是多张业务表关联
  • 最终结果还要落到临时结果集或中间表中

这些因素叠加之后,SQL 很容易从一条普通查询,变成一条输入集合很大、字段很多、日期展开明显、关联关系也不轻的重型 SQL。

这时候慢的地方,不一定是底表扫描本身,也可能出现在更前面的环节:

  • SQL 文本本身变长
  • 解析和计划生成成本增加
  • 批次输入过大,导致单次执行时间过长
  • 日期范围展开后,中间结果规模迅速放大
  • 多表关联让整条语句变得更宽、更重

所以回溯 SQL 调优,不能只盯着“底层引擎查得快不快”,而是要把“输入集合大小”和“展开后规模”一起看。

1. 先做 SQL 体积和工作量估算

先估 SQL 文本长度:

SQL 文本长度 ≈ batchSize × (平均 CID 长度 + 4~8 个 SQL 包装字符) + 固定 SQL 模板长度

如果平均一个 CID 长度是 32,那么可以粗略估一下:

batchSize = 1000
VALUES 段大小大约几十 KB,通常比较稳

batchSize = 10000
VALUES 段大小可能直接上几百 KB
再叠加字段列表、多表 JOIN、日期展开子句之后,整条 SQL 会明显变重

再估展开后的潜在工作量:

潜在展开行数上限 ≈ batchSize × dayCount

比如:

1000 CID × 1 天  -> 上限约 1000 行
1000 CID × 15 天 -> 上限约 1.5 万行
1000 CID × 30 天 -> 上限约 3 万行
5000 CID × 15 天 -> 上限约 7.5 万行

这时候就会很直观地看到,真正变重的并不只是 CID 数量,而是 CID 数量和回溯范围一起把单批工作量放大了。

2. 结合压测找稳定区间

前面的估算只是为了先把量级算出来,真正落地还是要压测。

比较实用的一种压测方式是,固定几组典型场景去跑:

  • 短区间场景,比如1 天
  • 中等区间场景,比如7 天
  • 长区间场景,比如15 天30 天

然后在每个场景下,分别测试几组批次规模,比如:

  • 500
  • 1000
  • 2000
  • 5000

每组重点看这几个指标:

  • 单批 SQL 生成后的文本长度
  • 单批执行耗时
  • 失败率和重试率
  • 多批次连续执行时的稳定性
  • 多任务并发时是否开始明显抖动

最后要找的不是“理论最大批次”,而是一个比较稳的区间。

比如有可能压测下来会发现:

  • 500 太保守,批次数明显过多
  • 10002000 这段整体比较平衡
  • 5000 在短区间下还能接受,但一旦叠上长日期范围就明显变重

那实际落地时,就更适合优先选中间这段稳定区间,而不是直接冲最大值。

3. SQL 体积调优时值得重点关注的方向

如果后面再做类似的大数据回溯任务,我会优先从下面几个方向去看。

第一,先看数据量级,不要一上来就只谈查询慢不慢。

在没有量级概念之前,直接讨论怎么调优其实意义不大。

要先知道:

  • 单任务通常会有多少 CID
  • 高峰期会同时跑多少任务
  • 回溯范围通常覆盖多少天
  • 一次查询大概会关联多少张表

这几个量级不清楚,后面很多判断都只能靠猜。

第二,不要只看 SQL 文本长度,也要看展开后的真实工作量。

很多 SQL 从文本上看并不算离谱,但一旦叠上日期范围和多表关联,单批工作量就会快速变重。

第三,先看单批执行稳定性,再看总吞吐。

对于批任务系统来说,稳定性通常比极限吞吐更重要。

单批可控、失败可恢复、执行时长比较稳定,这些收益往往比单纯少跑几批更有价值。

第四,不要只盯慢查询,要看整条链路。

回溯任务不是单纯的一条 SQL。

它通常是一整条链路:

  • 输入集合准备
  • 缓存写入
  • 批量读取
  • SQL 拼装
  • 查询执行
  • 结果写入
  • 失败重试
  • 任务收口

哪一段变重,最终都会体现在“任务慢了”这个现象上。只盯着查询引擎本身,很容易漏掉真正的问题。

总结

这次回头整理这部分经验,最大的感受是,CID 缓存和 SQL 体积调优看起来是两个问题,实际上刚好对应了回溯任务里最核心的两层能力:

  • 输入集合怎么稳定管理
  • 单批查询怎么可控执行

CID 缓存这一层,更关注的是存储成本、读取方式和生命周期管理。SQL 体积这一层,更关注的是单批工作量、日期展开规模以及整体执行稳定性。

如果用一句话收一下,我会把这部分经验记成这样:

在大数据回溯场景下,CID 缓存的关键是让输入集合存得稳、读得顺;SQL 调优的关键是让单批执行成本可控、可恢复、可预测。


评论