开发者发现 AgentRAM 存储-读取延迟达 3 秒,排除代码和数据库本身后,发现 API 与数据库部署在不同云区域,每次查询跨区域往返导致延迟。
大约一天的时间里,我认为 AgentRAM 慢到了一个我无法修复的程度。
整个产品的卖点就是简单。一个面向 AI Agent 的记忆 API,一次调用存储,一次调用召回,无需向量数据库,无需嵌入模型,没有任何模型坐在请求链路里每次额外增加半秒。选择它而不是更重的方案的唯一理由就是它应该感觉上是即时的。所以当一次简单的存储后立即召回开始耗时三秒时,这不仅仅是一个 bug,而是感觉整个前提都错了。三秒。对于一个键值读取来说。我想把整个排查过程讲出来,因为我在找到真正的罪魁祸首之前两次找错了方向,而真正的那个原因几乎可以说是无聊透顶。
首先要怪罪自己的代码是很自然的事,而且通常你是对的。所以我逐行阅读了请求路径。每个接触数据库的步骤都是单一操作。没有循环,没有等待任何不需要的东西。没有任何查询藏在一个辅助函数里再跑十次,没有意外的全局扫描,没有工作被做了两次。我数了数到数据库的往返次数,一只手数得过来,而且每一次都有存在的理由。不过我还是在每个步骤周围加了计时,因为读代码和测代码是两码事,只有第二种才算数。计时结果说处理器逻辑没问题,所有我自己写的都是个位毫秒级。三秒发生在我没看到的地方,这是三秒最不应该待的地方。
如果问题不在我的代码,而时间又消耗在我代码和数据之间的某个地方,数据库就是下一个自然要怀疑的对象。也许查询规划器做了什么蠢事。也许我漏了一个索引,测试表里的两行数据在欺骗我,让我误以为真实查询也会那样表现。所以我直接查了查询语句,对着数据库跑了一下并开启了计时。它们在几毫秒内就返回了。当然会这样,因为根本没有几行数据。数据库不慢。数据库坐在那里纳闷我凭什么冤枉它。这就是调试中没人告诉你的那部分。你排除掉了两个嫌疑对象,然而非但没有感到离真相更近,反而感觉更糟了,因为时间确实在被消耗,你能从时钟上看到,而你现在已经证明了它并没有花在两个明显应该花在那里地方的任何一个地方。延迟是真实的,但它没有归属。
我一直在给操作计时。我没有在给距离计时。
我的 API 运行在一个托管平台上。我的数据库运行在另一个平台。两者都不错,我如果再选一次还是会选它们。但我在不同的时间、不同的日子里分别搭建了它们,想的是不同的事情,我从来没有停下来问过它们在物理上到底在哪里。它们在不同的区域。我的 API 发出的每一个查询都从世界的一角出发,穿越到另一角,再回来。不是每个请求一次,而是每个查询一次,而一个请求会发起好几个查询。数据库实际只做了几毫秒的工作,但被包在一个往返旅程里,每次都绕了大半个地球然后回来,一个请求好几次。那就是那三秒。不是我的代码,不是查询语句,只是我从来没看过的地理位置——因为两个服务之间的延迟感觉应该是零,直到有一天它非常大声地告诉你是零。还有第二个更安静的作祟者紧挨着第一个。安静一段时间后的第一个请求总是最慢的,明显比后面的那些更慢。冷连接。没有什么在保持路径的热度,所以任何安静间隙的第一个访问者要付出去唤醒整个链路的代价。
我把 API 移到了和数据库同一个区域。那一下就解决掉了大部分。查询仍然做同样微小的工作量,只是再也不需要环游世界了。然后我给健康检查安排了一个真正的任务。有一个监控器在持续 ping 服务以确认它还活着,我让那个 ping 保持路径温热,这样一个安静的早晨的第一个真实用户就不必付启动引擎的代价。监控器替用户付,按计划来,而且从不抱怨。三秒变成了大约零点七五秒。同样的代码,同样的数据库,同样的两行数据。我只改了东西之间的相对位置,以及路径是否保持清醒。我没有优化任何一个查询,没有重写任何东西。我只是把两样东西挪近了一点,然后留了一盏灯。
这是我没有预料到会在意的部分。问题修复之后,我发出测试请求,看着它在不到一秒内返回,那时已经很晚了,只有我一个人醒着。而那个快速响应是来自一个我自己构建的服务,其他的人——一些我可能永远不会见面的开发者——会从他们自己的 Agent 里调用它。这个认知比修复本身更让人心头一沉。屏幕上的数字很小。但它刚刚跨越的距离——我和使用这个服务的任何人之间的距离——才是我坐下来认真思考的东西。然后我合上了笔记本电脑,因为太晚了。如果你在跑两个托管服务,而什么东西感觉神秘地慢:在你把自己的代码翻个底朝天之前,先检查一下它们的物理位置在哪里,再检查一下安静间隙的第一个请求是不是那个慢请求。距离和冷启动不会出现在你的代码审查里,因为它们不在你的代码里。它们在两个方框之间的空间里,而那是你最不会想到要去读的地方。我差点重写了一个根本不是问题的东西。真正的问题是:我从来没有问过我的两个服务它们是不是住在同一个城市。
那个 bug——你在找到真正的罪魁祸首之前花了好几个小时怀疑是自己的代码——是哪一种?我想听听。
(我在构建 AgentRAM(一个面向 AI Agent 的记忆 API,完全开源)的过程中遇到了这个问题。不过故事本身才是重点,讲讲你自己的吧。)