登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

RedisTimeSeries TS.ADD 的 ON_DUPLICATE LAST 和 SUM 有什么区别

来源:17golang原创

时间:2026-11-05 12:35:11 185浏览 收藏

我第一次遇到这个问题,是在补采一段 RedisTimeSeries 数据时发现某个时间点的数值突然翻倍。写入请求本身没有报错,时间戳也没有变化,真正改变结果的是重试请求里带了 ON_DUPLICATE SUM。如果这类重试代表“同一条采样再次送达”,应该覆盖旧值;如果每次上报代表一笔需要累计的增量,才应该相加。

官方地址:https://redis.io/docs/latest/commands/ts.add/

一句话判断:ON_DUPLICATE LAST 把同一时间戳的新值当作修正版,覆盖原值;ON_DUPLICATE SUM 把新值当作同一时间戳下的新增量,与旧值相加。两者都只覆盖当前这一次 TS.ADD 调用,不会替你修改时间序列的长期配置。

一次重复上报为什么会影响整段指标

这类异常的影响面通常不在所有时间点,而只集中在重复写入的时间戳。设备采样、消息重放、网络重试和补数据脚本都可能把同一条记录送来两次。若原始采样是 10,重试请求又带着 12:

  • 用 LAST,这个时间点最终表达“最新一次收到的值”,结果是 12;
  • 用 SUM,这个时间点最终表达“两个值的累计”,结果是 22。

所以排查时不要只看命令是否成功,还要先回答:重复请求是同一采样的重放,还是两次应该累计的增量?这个业务含义决定策略。

RedisTimeSeries TS.ADD 输入、重复时间戳和 LAST、SUM 两种策略的静态关系图
图1:TS.ADD 重复时间戳的策略对照说明图,LAST 表示覆盖,SUM 表示累加,不是运行截图。

时间线里真正的触发条件:时间戳相同

TS.ADD 的时间戳以毫秒为单位,也可以使用 * 让服务端生成当前时间。只有两次采样落在同一个时间戳时,重复策略才会参与决定如何处理旧值。时间戳不同,就不是这篇文章讨论的重复点。

用两个独立的时间序列做对照,可以避免把前一个实验的结果混到后一个实验里:

# 固定一个毫秒时间戳,模拟同一条采样的首次写入
redis-cli TS.ADD meter:last 1710000000000 10

# 同一时间戳再次到达时,用新值覆盖旧值
redis-cli TS.ADD meter:last 1710000000000 12 ON_DUPLICATE LAST

# 读取这个时间点,观察 LAST 的最终值
redis-cli TS.RANGE meter:last 1710000000000 1710000000000

这个例子中,第二次写入表达“12 是这个采样点的修正版”,因此最终值应按 12 理解。这里的关键不是第二次请求更新了整条序列,而是它在同一个时间戳上覆盖了原来的样本。

根因对比:LAST 是替换,SUM 是累计

我后来把异常请求拆成两个最小场景:一个场景模拟设备重试,另一个场景模拟电量脉冲或订单件数这类增量。命令结构很像,但读数含义完全不同。

# 首次写入 10,准备一个可以被重试覆盖的采样点
redis-cli TS.ADD meter:last 1710000000000 10

# 重试带来修正值 12,LAST 只保留这次新值
redis-cli TS.ADD meter:last 1710000000000 12 ON_DUPLICATE LAST

# 首次写入 10,准备一个需要累加增量的采样点
redis-cli TS.ADD meter:sum 1710000000000 10

# 第二个请求代表新增 12,SUM 将旧值和新值相加
redis-cli TS.ADD meter:sum 1710000000000 12 ON_DUPLICATE SUM

# 分别读取两个点,确认比较的是业务语义而不是命令返回格式
redis-cli TS.RANGE meter:last 1710000000000 1710000000000
redis-cli TS.RANGE meter:sum 1710000000000 1710000000000

对这组输入,meter:last 的点应理解为 12,meter:sum 的点应理解为 22。官方命令语义也是如此:LAST 用新值覆盖,SUM 在已有样本时计算“旧值 + 新值”;如果该时间戳还没有旧样本,SUM 就直接使用新值。

这解释了故障的根因:把“重复送达”误判成“新增事件”,会让 SUM 在消息重试时重复计数;反过来,把本应累加的脉冲数据改成 LAST,又会丢掉前一次增量。

修复动作:把策略限制在当前一次 TS.ADD

ON_DUPLICATE 是一次命令级覆盖参数。它只影响当前这条 TS.ADD 如何处理同一时间戳的重复样本,不会把时间序列的长期重复策略永久改掉。这一点很适合修复边界明确的回放任务:你可以让一次补数任务使用 LAST,而不必把整个序列的后续写入逻辑一起改掉。

修复时我会先给数据源分类,再把策略写在调用点:

数据来源重复请求代表什么单次 TS.ADD 建议
设备心跳、周期采样同一采样的重试或重放ON_DUPLICATE LAST
补采脚本修正历史值人工确认后的新版本ON_DUPLICATE LAST
脉冲计数、订单件数增量应该合并的新增量ON_DUPLICATE SUM
无法确定的混合来源请求身份和业务含义不清先暂停自动累加,补齐来源字段

需要特别注意:如果命令同时负责创建一个不存在的时间序列,ON_DUPLICATE 对这次创建没有可比较的旧样本,因此不会产生覆盖或累加效果。真正的策略差异出现在目标时间序列已经存在、且目标时间戳已有样本时。

采样来源、时间戳、重试请求与 LAST、SUM 业务含义之间的静态边界
图2:重复上报修复边界说明图,帮助区分幂等覆盖和增量累计,不是终端或 Redis 控制台截图。

防复发:让重试和新增量在入口就分开

只替换命令参数还不够。下一次排查时,至少要保留采样来源、业务事件标识和采样时间戳,让读者能判断两个请求是不是同一条数据。对于可重试的采样上报,调用方应保持同一个时间戳和事件身份;对于累计事件,应该明确每次请求到底代表一个新事件还是同一个事件的重放。

  • 在日志中同时记录序列 key、时间戳、上报来源和选择的重复策略;
  • 把“消息重试”与“业务新增量”分成不同的写入路径,不要只靠调用方口头约定;
  • 修复历史数据前,先确认同一时间戳的旧值是否已经包含部分累计量;
  • 将 LAST 或 SUM 写入代码注释和接口文档,避免后续维护者把它当成可互换参数。

最后的判断

如果你面对的是“同一采样再次送达”,选 ON_DUPLICATE LAST;如果你面对的是“同一时间点还要合并一笔新发生的增量”,选 ON_DUPLICATE SUM。两者的命令形式几乎只差一个单词,但它们表达的是覆盖模型和累计模型的区别。发布修复前,先根据数据来源确认重复请求的业务含义,再把策略限定在需要它的那次 TS.ADD。

相关问题

ON_DUPLICATE SUM 第一次写入会不会把值加倍?

不会。指定时间戳没有旧样本时,SUM 使用新值;只有已有样本时才进行旧值与新值相加。

ON_DUPLICATE LAST 会修改整个时间序列的默认策略吗?

不会。它是当前命令的覆盖参数,文章讨论的范围只包括这一次 TS.ADD 的重复样本处理。

两个请求时间戳不同,还需要比较 LAST 和 SUM 吗?

如果时间戳不同,就不属于同一时间戳重复写入。此时应先检查采样时间是否被错误转换、截断或回退,再判断是否需要重复策略。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>