跳到正文

默认分类 ·

这四个坑,我都种进代码里了

最近在写一个企业多Agent智能体项目,以前我总以为,生产事故的沉淀方式是复盘文档。后来发现,文档写完了坑还在,只是坑底多了个会写文档的人。真正的沉淀是把坑砌进代码里:注释写清楚"这地方曾经死过人",逻辑写清楚"这里不许再死第二次"。

第一个坑:跨事件循环 checkpointer

AsyncPostgresSaver 的 asyncio.Lock 有户籍观念,在哪条事件循环里出生,就只认哪条循环。HTTP 请求在一个 loop 上排队,钉钉 Stream 在另一个 loop 上关灯睡觉。你拿着 HTTP 循环里造出来的 saver 去钉钉循环里用,它不报"不认识你",它报一个全家都看不懂的错。

于是代码变成了这样:

loop_id = id(asyncio.get_running_loop())
with self._lock:
    cached = self._entries.get(loop_id)
...
async with build_lock:
    with self._lock:
        cached = self._entries.get(loop_id)

一个 asyncio 的锁不能跨循环,但一个 threading.Lock 管着所有循环。翻译成人话:锁可以单相思,看门的必须海王。

第二个坑:钉钉重投去重

钉钉 Stream 掉线重连之后,没 ACK 的回调会被重投。如果你的进程刚好重建过,进程内那张去重表又白得像新买的本子,于是用户问一句,机器人答两次。产品叫它"智能助手",用户叫它"复读机"。

修复是 Redis SET NX:

grabbed = await client.set(
    self._key(message_id),
    "processing",
    nx=True,
    ex=self.PROCESSING_TTL_SECONDS,
)

键存在就拦下,处理完把 processing 覆盖成 done,失败则 release 把窗口重新打开,但做完之后不许再撤销拦截。一句话总结:钉钉可以重投,答案不能重发。问一次答两次,在客服侧叫事故,在复读机侧才叫功能。

第三个坑:kNN 动态映射

OpenSearch 很贴心:索引不存在?第一次写文档时自动给你建。也很坑:自动建出来的映射把 embedding 当成普通 float 数组,kNN 查询从此静默失效。"静默"是所有故障里最残忍的两个字,比报错可怕十倍。报错至少还吵你,静默是它把嘴闭上,看着你上线,看着你开会,看着周报里写"检索一切正常"。

所以代码里必须有这一步:

"embedding": {
    "type": "knn_vector",
    "dimension": CHUNK_EMBEDDING_DIMENSION,
}

而且启动时建一次,入库前再兜底一次,并发 PUT 撞车就认 resource_already_exists_exception。就像出门检查两次门锁:一次是习惯,一次是上次没锁门留下的心理阴影。

第四个坑:进程内后端的扩容警告

本地开发舒服得很:checkpointer 是内存的,审批是内存的,线程归属是内存的,长期记忆也是内存的。一切都好,直到你想上两台机器。那一刻你才明白,这套系统的横向扩展就是横向散架:会话断了,审批丢了,用户 A 的线程在机器 1 上被人从机器 2 访问,403 一道闪电劈下来。

所以启动时我们大声警告:

if degraded_backends:
    logger.warning(
        "single-process backends active (%s): horizontally scaling this "
        "service or running multiple uvicorn workers will break session, "
        "approval and ownership state; configure DATABASE_URL / "
        "APPROVAL_STORE_URL before scaling.",
        ", ".join(degraded_backends),
    )

注意,这不阻断启动,只是大声。父爱如山,也可以像日志一样吵。

踩坑的最高境界不是把坑绕过去,而是把坑变成代码里的紧箍咒。每个注释里都有一次深夜,每个 nx=True 背后都有一个卸载复读机的用户。生产环境教我的事不多,但每件都很贵:锁有户籍,重投会串门,映射会装死,内存会失忆。现在我写代码,心里只有一句话:这地方,别再死第二次了。

评论

还没有人说话。

说点什么

邮箱只用于显示头像,不会公开。 取消回复