摘要

  • UIDBATCHES 2000 中的 2000 是每个区间所含现存邮件的硬上限,不是精确数量承诺;服务器可以返回更小的批次,区间首尾 UID 也未必对应真实邮件。
  • 可审计结案必须保存账号、邮箱、UIDVALIDITY、计划版本、命令标签、并发 EXISTS/EXPUNGE/VANISHED 变化,以及后续 FETCH、STORE、COPY、MOVE 等命令对每封邮件产生的终态;规划成功不能代替执行回执。

先看一个“不存在的端点”

服务器返回区间 163886:99703。运营界面很自然地把 163886 和 99703 渲染成两封边界邮件,再把二者之间的数字差解释为批次数量。可 RFC 10022 明确允许这两个 UID 都没有对应的现存邮件。

这不是数据损坏。UID 空间本来就会因删除留下洞,服务器也可以选择更便于实现的边界。区间表达的是“后续命令应在这段 UID 空间内寻找当前存在的邮件”,而不是“首尾各有一封邮件,且中间连续填满”。最旧批次甚至可以落到 UID 1,即使最低的真实 UID 是 302;数字 1 只负责表达“已经到底”,不负责创造邮件。

如果系统在这一步就把边界当对象,错误会层层扩散。计划库会生成两条虚假的邮件记录;进度条会用数字宽度估算数量;审计报告会把“区间覆盖”写成“邮件已发现”。等到 FETCH 找不到端点时,团队又会把协议允许的空洞误报成丢信事故。

RFC 10022 的价值恰恰在于允许粗粒度边界与真实邮件集合分离。它给客户端一个可复用的工作划分,却没有假装活邮箱是一张静态数组。

2000 只向上负责

客户端请求 UIDBATCHES 2000 后,服务器返回的任何一个区间都不得包含超过 2000 封现存邮件。这是硬约束。

反方向没有同样的保证。服务器应尽量接近请求规模,在可行时争取达到 90%,但可以因为实现显著更简单、更高效,或因为计算期间邮箱发生变化而返回更少邮件。一次合法结果可能是 1990、1977 和 2000,而最后一个批次只装余数。

因此,2000 是资源预算,不是库存回执。调度器可以据此限制单任务的内存、带宽或并发;它不能提前登记“已处理 2000”。真正数量要从后续命令实际命中的 UID 集合读取。

请求规模还有 500 的下限。再小就应收到 TOOFEW。这个下限不仅保护服务器免受高频精细计算,也保护 UIDONLY 的设计意图。UIDONLY 移除了消息序号;如果 UIDBATCHES 允许一封一批,客户端就能换一条路径重建精确位置。标准提供的是批处理能力,不是对位置限制的绕行。

三方权限由此分开:客户端选择可承受的上限;服务器依照自身存储结构划界;标准维护共同的最小和最大约束。谁都不能把上限改写成精确事实。

四个区间并没有锁住 6823 封邮件

RFC 10022 的示例邮箱有 6823 封邮件。请求规模为 2000 时,服务器返回四个由高 UID 到低 UID 排列的区间。规划系统可能因此出现四个绿色任务框。

在同一个邮箱世代里,新邮件取得更高 UID,不会插进已经返回的旧区间。这让旧计划具有稳定的几何边界。但删除会让区间失去成员。第三个区间规划时约有 2000 封,执行 FETCH 时可能只剩 1988 封。区间仍然有效,“处理了 2000 封”却已经失真。

新到邮件出现在原最高 UID 之上。它们不破坏四个旧区间,却会破坏“已覆盖整个邮箱”这句话。若任务定义是“处理 09:00 已存在的邮件”,新邮件可以进入下一版本;若任务是连续同步,就应立即开启新的高 UID 批次。命令语法不会替业务决定截止线。

重算也不是随时可用的刷新按钮。只有切换邮箱、删除数量超过半个批次,或新增数量超过半个批次时,客户端才适合再次请求。否则不得重发,服务器也应以 LIMIT 约束滥用。

这里存在两种有效期。协议有效期回答“旧区间还能不能用”;业务有效期回答“旧区间还够不够支持承诺”。半批阈值控制计算成本,不会自动满足迁移、留存或法律冻结的要求。

空响应也可以成功

服务器即使没有任何区间可返回,也必须发送带命令关联标签的 UIDBATCHES 非标记响应,再完成命令。空邮箱会产生这种结果,请求了不存在的批次编号也会。

假设邮箱只有四个批次,客户端请求 6:8。服务器返回空区间并以 OK 结束,含义是“这次合法请求在指定窗口内没有区间”,而不是“整个邮箱没有邮件”。

若日志只保存 OK,问题本身就丢了。解释空结果至少需要账号、所选邮箱、UIDVALIDITY、命令标签、请求规模、批次窗口和时间。它们共同构成证据封套。

其他状态也不可压成一个“分页失败”。TOOFEW 表示粒度太细;TOOMANY 表示窗口或完整结果超出范围;LIMIT 表示重算压力越界;把窗口写成 4:1 而不是 1:4,可以得到 CLIENTBUG。每种结果对应不同的修复权限和风险。

一个系统若丢掉这些差异,就会把公开协议重新变成供应商私有猜谜。运营者只能依据产品行为推测限制,无法用共同语义判断应缩小窗口、增大批次还是等待状态变化。

十万是共同能力,不是无限索取

带批次窗口的请求不能跨越超过 100000 封邮件,服务器至少要具备返回这一规模区间的能力。面对极大邮箱,客户端不带窗口地索取所有区间时,服务器仍可回复 TOOMANY。

这条边界同时约束双方。服务器不能只登记能力名称,却连共同最低规模都无法服务;客户端也不能因为命令已标准化,就要求服务器承担无限全局计算。

大邮箱应采用分窗读取。由于两次计算之间邮箱可能变化,RFC 10022 建议让相邻窗口重叠,例如先取 1:100,再取 100:200。重复的第 100 批是见证点:若两次结果不同,客户端看见了漂移。

重叠不能冻结邮箱,也不会自动裁决哪一版符合业务目标。它购买的是可见性。若客户端在比较前把重复区间去重,额外成本已经发生,最有价值的证据却被删除。

成熟实现会保留两次响应、各自时间、期间的邮箱变化以及最终处置。重叠不是浪费,而是局部可验证性。

相邻扩展各有自己的动词

UIDONLY 激活后,客户端不得再使用消息序号,服务器也不得返回序号;删除通知使用 VANISHED。UIDBATCHES 提供粗粒度 UID 区间,并没有把逐封位置权力还给客户端。

PARTIAL 分页的是实际 SEARCH 结果,或限制一次实际 UID FETCH 的返回。UIDBATCHES 先生成可供多种后续命令使用的边界。前者是查询或读取的一部分结果,后者还是计划。

SEARCHRES 用 $ 保存上一次搜索结果。RFC 10022 明确禁止 UIDBATCHES 改写 $,因为它既不是 SEARCH,也不是 UID SEARCH。近似区间不能暗中升级成已证实的邮件集合。

QRESYNC 与 CONDSTORE 能提供修改序列和 VANISHED 等变化证据,帮助客户端发现计划已经漂移。它们无法追溯性地把旧计划变成新快照。

MESSAGELIMIT 则约束后续命令真正能处理多少邮件。若服务器为 FETCH 等操作宣布 1000 的限制,客户端就应把 UIDBATCHES 规模控制在 1000 以内。即便一个 2000 规模的区间本身合法,也不代表一次 FETCH 可以越过另一份合同。

CAPABILITY 中出现多个名称,不等于它们自动合成一个全能能力。可执行边界是各扩展、具体动词和当前邮箱状态的交集。

用七份名单代替一条进度条

结案时至少应区分以下集合:

集合 要回答的问题
P 哪些邮件属于本任务政策与邮箱世代?
B 计划生成时,哪些现存邮件可由这些区间触达?
A 哪些邮件真正收到后续命令?
C 哪些邮件获得了被接受的终态结果?
X 哪些预期邮件被观察为已删除或消失?
N 哪些新邮件出现在原最高 UID 之上?
U 哪些结果仍未知或需要重试?

一次有截止线的导出可以要求 P = C ∪ X、U 为空,并为 X 的每个成员保留原因;N 交给下一版本。连续同步可以立即为 N 建批。只读索引可以把合法删除当作终态,而不是故障。

数字相等还不够。一封预期邮件消失,同时一封新邮件处理成功,总数看似平衡,成员却已换人。对账键必须包含邮箱、UIDVALIDITY、UID 与任务版本。

这正符合 Heng Lu 所强调的最小共同层:共同语义负责让独立参与者交换可验证事实,后续选择留给实际运行者。标准无需成为迁移政策、留存政策或完成判定的中央权威。

公开记录没有证明什么

RFC 10022 没有证明任何具名邮箱服务已经部署 UIDBATCHES,也没有证明某个客户端遵守重算纪律,或某台服务器在生产中达到 90% 建议值。本文场景是依据规范构造的运营测试,不是事故报道。

IANA 登记让 UIDBATCHES、TOOFEW 与 TOOMANY 具有共同含义,却不会检查运行中的二进制。CAPABILITY 是声明层;区间响应是计划状态层;后续命令是否命中并完成,是效果层。

“运行代码优先”也不是让实现随意改写规范,而是让实际效果有权反驳过度总结。区间可以完全合规,进度条仍可能错误。文档与运行事实各守边界,才形成可问责的现实层。

来源