DynamoDB Streams: ExpiredIteratorException

TL;DR — 来自 GetShardIterator 的分片迭代器只有 15 分钟有效。如果你的消费者暂停了、缓慢地处理了一批数据,或者跨重启持有了一个迭代器,下一次 GetRecords 调用就会抛出它。恢复方法是请求一个新迭代器——用 AFTER_SEQUENCE_NUMBER 配上你处理过的最后一个序列号,这样你就能从中断处精确地继续。

含义

ExpiredIteratorException: The provided iterator exceeds the maximum age allowed.

直接读取一个 DynamoDB 流是一个两步循环:GetShardIterator 给你一个指向某个分片的位置句柄,而每次 GetRecords 调用会消耗它并返回下一个(NextShardIterator)。这些句柄中的每一个都会在发出后 15 分钟过期。这个异常并不意味着数据没了——流记录会保留 24 小时——它只意味着这个_位置句柄_陈旧了,你需要一个新的。

为什么会发生

  • 两次轮询之间处理缓慢——每批所做的工作(写入、外部调用)在下一次 GetRecords 之前花了超过 15 分钟。
  • 一个暂停或挂起的消费者——一个停止的 worker、一次漫长的部署,或一个持有循环的调试器会话。
  • 存储迭代器——把迭代器字符串(在某个数据库或检查点中)持久化并稍后重用;迭代器是短暂的,序列号则不是。
  • 退避拖得太久——一个错误重试循环睡过了那个时间窗口。

如何修复

  1. 捕获它并从你上一个检查点重新播种迭代器——跟踪你处理过的最后一条记录的 SequenceNumber,然后:

    const {ShardIterator} = await streams.send(
      new GetShardIteratorCommand({
        StreamArn: streamArn,
        ShardId: shardId,
        ShardIteratorType: 'AFTER_SEQUENCE_NUMBER',
        SequenceNumber: lastProcessedSequenceNumber
      })
    );

    还没有检查点?TRIM_HORIZON 会从最旧的可用记录开始重新处理该分片;LATEST 会跳到新活动。

  2. 检查点记录序列号,绝不记录迭代器——序列号才是持久的恢复点。

  3. 保持轮询循环紧凑——异步地做繁重的逐记录工作(把它排入队列),让 GetRecords 的节奏稳稳地保持在 15 分钟以内。

  4. 优先使用托管消费者——Lambda 事件源映射和 DynamoDB Streams Kinesis 适配器(KCL)会为你管理迭代器和检查点;手工编写的 GetRecords 循环正是这个异常出没之处。

确保你的处理是幂等的:从一个检查点恢复可能会重新投递一条你处理了一半的记录。流本身对每条记录恰好包含一次,但一个从上一个检查点重新播种的消费者实际上是至少一次地处理。

当你能看到项目在变化时,调试一个流实际发出了什么会容易得多——DynoTable 桌面应用显示一个项目的实时状态,让你能把流记录与数据对上。在估算一次追赶重放的读取成本?DynamoDB 定价计算器帮你搞定。

相关错误

References

最后核实于 2026-07-13,依据上方链接的 AWS 官方文档。

无需控制台即可使用 DynamoDB

DynoTable 是面向 DynamoDB 的快速桌面客户端 —— 浏览表、运行 SQL 风格的查询,并在本地编辑项。