那次讨论从一个很直接的问题开始:如果有10个“6人一单”,是不是提交第1批之后,就可以马上提交第2批,不必等第1批生成结束?表面上看,这是在问并发够不够高;真正要确认的,却是连续提交时,上一批的参考图、人物和状态会不会悄悄进入下一批。
我当时没有把“能不能连续点提交”直接等同于“批量流程已经可靠”。一次提交不阻塞,只能说明提交动作把任务交给了后台,当前操作不用停在原地等待。它没有回答输入是否独立,也没有回答最后取回的结果能不能对应到原来的批次。如果这两个问题没处理好,速度越快,错配就越快,等到结果出来时反而更难追查。
表面上是并发,实际上是边界
连续提交多个批次,最容易让人先想到的是把并发数调大。比如一次发10个任务,或者把一批里放更多人物和参考图,看起来都能缩短等待时间。但这只是改变了任务同时运行的数量,没有定义任务之间的边界。
这次的场景很具体:前面已经取回了6个不同的人,接下来要确认这6个输入能不能被完整地送入一次提交;之后再准备下一批时,不能让上一批还留在临时状态里。否则,下一批即使只想处理新素材,实际送出的内容也可能包含旧图片、旧人物,甚至沿用上一批的失败状态。
批量任务的第一条可靠性,不是同时跑多少个,而是每个批次能不能证明自己只带着自己的输入出发。
这件事让我重新意识到,速度和隔离不是一对互相排斥的选择。真正拖慢流程的,往往不是提交前多做了一次清空,而是提交以后发现结果混了,只能靠人工回忆、重新比对,甚至整批重做。
先把“提交不阻塞”说清楚
我会把提交动作和生成结果分成两个阶段。提交阶段负责准备输入、发出请求、记录回执;生成阶段由后台继续运行。只要提交阶段在拿到平台接受信息后就能返回,操作者就可以继续准备下一批,不必把“结果已经完成”当成下一次提交的前置条件。
但这里有一个容易被忽略的区别:不阻塞不等于不需要记录。每次提交都应该留下一个能追踪的批次号,以及这次提交包含了什么输入。没有回执的连续点击,只是把任务推了出去,无法证明第3批和第4批分别对应什么,也无法在某一批失败时只重试那一批。
如果不处理这个区别,后果会很实际:前面的批次还在生成,操作者继续往下提交;等到结果陆续出现时,列表里只有一堆成品,却没人能确定每张图来自哪一批。此时再去问“是不是生成错了”,通常已经错过了最容易定位的时间点。
我选择把“提交成功”和“结果完成”看成两种不同的状态,是因为它们影响的是两件不同的事。前者决定能不能继续往下发,后者决定能不能验收和交付。把两者混成一个状态,流程要么被不必要地卡住,要么在没有证据时误以为任务已经可靠。
清空不是形式,而是交接点
批次隔离最关键的动作发生在提交前。上一批完成后再清空,已经太晚了,因为污染往往在下一批提交的那一刻就发生。更稳妥的做法是:先为新批次建立自己的输入快照,再清空承载临时素材的容器,最后只把这一次需要的内容放进去。
这里的“清空”不是把所有历史都删除,更不是把已经提交的任务抹掉。它清理的是下一次提交会读取的临时状态。已提交批次的回执和输入快照应该被保留下来,因为它们是后面回收结果和排查失败的依据。
当时用户特别强调,每次提交都要从一个清空的状态开始。这句话的价值不在于增加一个按钮动作,而在于规定了交接点:上一批的工作到哪里算结束,下一批从哪里重新开始。没有这个交接点,批量处理就只能依赖操作者记得“刚才留下了什么”。
最容易误判的错误捷径,是认为只要界面上当前显示的是新人物,下一次提交就一定只会带上新人物。界面显示可能更新了,缓存、数组、临时目录或待提交队列却未必已经更新。如果输入来源不止一个,这个假设尤其危险。
清空的意义不是让系统看起来干净,而是让下一批的输入范围可以被重新证明。
结果回收也要按批次对应
提交和清空解决的是“发出去之前不要混”,结果回收解决的是“回来以后不要认错”。我会给每批建立唯一标识,并把提交时的输入快照、回执和最终结果放在同一条记录下。这样,回收结果时先找批次,再看结果,而不是看到一个新成品就凭感觉归类。
这一步很容易被省略,因为顺利时成品似乎会按提交顺序出现。但后台任务的完成时间可能不同,某一批可能因为素材数量、处理状态或临时错误而晚回来。按照出现顺序认领结果,实际上把后台的完成顺序误当成了提交顺序。
如果某批失败,我也不会重放整个队列。先保留失败原因,再只重试当前批次;重试前重新生成这一批的输入快照,并确认它没有把已经成功的批次重新带入。这样做的理由很简单:失败是局部的,修复范围也应该尽量局部。整队列重放虽然省了一次判断,却可能制造重复结果,把原本清楚的状态变得更乱。
结果回收至少要能回答3个问题:这是哪一批?这一批原本提交了什么?现在的结果是完成、失败,还是仍在处理中?只有这3个问题都能回答,连续提交才真正形成了闭环。
一人一图和多图测试要分开
后来我们把流程分成两路:一人一图的流程已经比较可靠,可以用于快速交付;一次提交多张图的流程暂时只做测试,不把它和可靠交付混在一起。这个分开很重要,因为“能提交”与“适合交付”是两个标准。
一次处理多张图,理论上可能更快,但它增加了输入对应、人物顺序和结果核验的复杂度。前面已经出现过结果不对的例子,这说明问题不是单纯把数量降下来就自动消失,而是多图流程仍需要单独验证。把测试流程直接当成生产流程,最大的风险是为了追求吞吐量,牺牲了交付结果的确定性。
我判断一人一图应该先作为交付主路,是因为它的边界更容易看清:一张参考图对应一个人物,一个提交回执对应一个任务,失败时也能只重做一个单元。多图流程则保留在测试路上,等输入隔离、顺序对应和结果回收都验证稳定后,再考虑扩大使用范围。
可靠的流程不一定是一次处理最多内容的流程,而是出错时能把影响范围关在一个小批次里的流程。
下次我会怎么做
下次遇到连续批量提交,我会先把任务拆成独立批次,而不是先讨论要开多少并发。每批先生成唯一标识和输入快照,提交前清空临时容器,只装入当前批次的素材;提交返回后立即记录回执,再继续准备下一批。这样,提交动作可以连续进行,但每次提交仍然有清楚的起点。
提交之后,我会按批次号回收结果,明确区分完成、失败和仍在处理。失败只重试当前批次,并保留原始失败原因;成功批次不因为后面某批出错而被整队列重放。最后再根据用途分流:已经验证可靠的一人一图流程用于交付,多图流程继续作为测试,不让实验性路径承担交付压力。
这套方法可以复用到很多异步工作里。真正需要留下的,不是“我当时连续点了10次”,而是每一次为什么可以继续、这一批带了什么、结果回来后如何证明它属于这里。一个流程成熟的标志,不是它能在顺利时跑得多快,而是它在批次增多、结果乱序或局部失败时,仍然知道该停在哪里、该重试什么,以及哪些已经完成的事情不应被再次触碰。