您在 Elevare Digital 的 AI 代理进入了空闲状态,原因是新添加的 PostgreSQL 行级安全性 (RLS) 策略过滤掉了所有的任务行,导致队列看起来是空的。直到任务堆积如山,团队才发现这个错误,并被迫重新设计编排器检测空队列的方式。
隐藏的盲点
ARIA 是 Elevare 的自主 AI 系统,它会轮询 PostgreSQL 表以获取待处理任务。查询成功执行并返回了零行数据,随后代理便进入了休眠状态。而实际上,表中任务满满。由于一项 RLS 策略将 SELECT 访问权限限制在特定的用户集中,编排器连接时使用的是一个缺乏“绕过 (bypass)”权限的服务角色,因此数据库在结果集中静默地剥离了每一行。PostgreSQL 将过滤后的读取视为与空表相同的状态,因此没有出现任何错误、警告或失败代码。空闲代理发出的正常心跳完全没有提示任何问题。
RLS 如何将满载队列变为死寂
RLS 在执行 SELECT 时会为每一行添加一个谓词 (predicate)。如果谓词为假,该行就会从结果中消失。客户端只能看到满足策略的行,永远不会知道有些行被隐藏了。对于队列工作器来说,空的结果集看起来与真正的空队列完全一样。编排器认定“无行 = 无任务”,随后进入了空闲循环,而任务却在幕后不断堆积。
团队发现,一项旨在将读取范围限制在单个用户的策略,无意中也限制了服务角色本身。由于该角色缺乏特殊的“绕过 RLS (bypass RLS)”属性,该策略应用于编排器发出的每一个查询。这说明了一个经典的“安全性 vs 可观测性”的权衡问题:RLS 保护数据免受未经授权的用户访问,但它也会为依赖可见性的系统组件移除一个有用的故障信号。
金丝雀检查模式 (The canary-check pattern)
为了打破对“静默空结果”的依赖,Elevare 增加了一个“金丝雀”检查。新的流程如下:
- 查询待处理任务表。
- 如果返回了行,则按原样进行处理。
- 如果结果为空,则针对一个必须始终存在的专用“金丝雀行”发起第二次查询。
- 如果金丝雀查询返回了预期的行,说明队列确实为空;记录一个空闲心跳。
- 如果金丝雀查询也返回空结果,说明代理处于“盲目”状态;立即触发警报。
现在,编排器可以区分三种状态:
- 发现任务 – 正常处理。
- 无任务,金丝雀正常 – 真正的空闲期。
- 无任务,金丝雀失败 – 隐藏的 RLS 拦截,触发警报。
金丝雀表是一个永远不会改变的单行表。设置它大约花费了一个小时,但它消除了一整类静默故障。
团队应该怎么做
如果您在 PostgreSQL 或基于其构建的托管服务(如 Supabase)上运行队列工作器,请遵循以下步骤:
- 使用带有“bypass RLS”标志的服务角色凭据。这允许系统组件无视用户级策略查看所有行。
- 审计 RLS 策略,检查是否为服务角色遗漏了绕过权限。对于终端用户看似正确的策略,可能会无意中困住内部服务。
- 添加一个金丝雀表(或等效的始终存在的行),并将金丝雀检查纳入工作器的空闲逻辑中。额外的查询开销很小,且能提供清晰的安全网。
权衡
RLS 仍然是实施细粒度数据访问的强大工具。它能防止意外的数据泄露,并支持多租户架构,而无需在整个代码库中散布应用层过滤器。其缺点是,它可能会向那些将简单的“无行”信号视为“无事可做”的组件隐藏故障。金丝雀模式并不会削弱 RLS 的安全性;它增加了一个轻量级的验证步骤,从而恢复了系统的可观测性。
启示
隐藏的 RLS 策略可以将繁忙的队列变成死寂的终点,导致 AI 代理在任务堆积时处于空闲状态。为服务角色授予适当的绕过权限,并将每次空队列读取与金丝雀检查相结合;这样团队就能确保其自主工作者的可靠性,并避免代价高昂的盲点。
