遥测建表撞权限就把整个 recorder 判死:CREATE TABLE IF NOT EXISTS 的权限检查早于存在性检查 #9
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
症状
应用账号对表有
INSERT权限、表也已经存在,遥测仍然整个降级成 no-op,只留一行 warning:之后这个进程里所有调用记录一条都不落库。业务调用一切正常,所以从外面完全看不出异常——
等到要用这批数据做分析时才发现整段历史是空的,那时候补不回来了。
复现
一个只被授予表级写权限、没有 schema 建表权的账号:
实测(PostgreSQL 16,
chs3_test账号):根因
telemetry/postgres.py的_ensure_ready()无条件执行建表 DDL:PostgreSQL 检查 schema 的
CREATE权限早于IF NOT EXISTS的存在性判断,所以表明明就在那儿、账号也明明写得进去,这一句照样被拒。异常落进外层的
except Exception,self._failed = True,recorder 永久 no-op。这个坑库里已经修过一次,只是没修在这一行
紧接着被调用的
_backfill_columns()处理的是同一类问题,它的 docstring 写得很清楚:CREATE TABLE这一行需要的正是同样这两条,而它两条都没有。所以现在的行为是:补列失败只丢一行日志接着干活,建表失败却把整个 recorder 判死——
而两者失败的原因、失败的账号、失败的机理完全一样。
建议的修法
和
_backfill_columns对称:先探测表在不在,在就跳过建表。顺带一条:即便建表真的失败了(表不存在、也建不出来),是不是也该像补列那样只
warning 而不置
_failed?那种情况下后续 INSERT 反正会逐行失败,行为一样,但少一个「一次初始化失败就永久关掉」的开关。这一条不确定,看你怎么权衡。
SQLite 那一侧同款:
sqlite.py也是构造期无条件建表。它一般不会撞上权限问题(文件权限是另一回事),但两侧守卫按你们的规矩要对称。
影响面
任何按最小权限授权的部署都会中招,而且中招的方式是静默的——这正是最难发现的那一类。
我们这边(CHSAnalyzer3)第一次端到端真跑,150 多次模型调用的耗时、token、成本全丢了,
是翻日志才发现的。
环境:polygateway 1.1.1,PostgreSQL 16,asyncpg。
成立,已在 1.1.2 修复并发布。感谢报告——尤其是把
_backfill_columns那段 docstring 翻出来对照,那正是同一个坑修了一半的证据。复现确认
在实验室实例上按你的场景造了临时角色(只授
SELECT, INSERT ON llm_calls,跑完删净),PostgreSQL 16.14 实测:SELECT to_regclass('llm_calls')INSERT INTO llm_calls ...CREATE TABLE IF NOT EXISTS llm_calls (...)ALTER TABLE ... ADD COLUMN IF NOT EXISTS机理与你判断的一致:建表走
RangeVarGetAndCheckCreationNamespace(),schema 的 aclcheck 在查 relid 之前且无条件执行。采纳了探测,但判死那条走得比建议更远
你提的
to_regclass探测已按原样采纳——补充一个当时没提到的好处:它与INSERT走同一套 search_path 解析,而裸CREATE TABLE落在首个可建的 schema,两者可能不是同一张表。所以探测优先不只是绕权限,口径也更准。你末尾那条"是不是也该像补列那样只 warning"我没有直接照做,而是换了个判据:判死只认「确定写不进去」,不认「初始化时出过错」。
理由是:只加探测的话,那个"一次异常永久关掉"的开关还在,只是换了触发口——初始化瞬间的 DB 抖动、一次
pool.acquire失败、search_path 配错,仍然会让整个进程静默失遥测。而全都不判死的话,表真的不存在时每次调用都要发一条注定失败的 INSERT 加一条 warning,而这种情形是可以确定判定的。SQLite 侧:实测后有意不对称
按你说的"两侧守卫要对称"去测了,结论是不该加:SQLite 对已存在的表在解析期就把
CREATE TABLE IF NOT EXISTS短路掉,既不抢写锁也不检查可写性——另一连接持BEGIN EXCLUSIVE、或文件chmod 444时该语句均通过(同条件下INSERT与新表名建表分别报 database is locked / readonly database)。所以 PG 那个坑在这边不存在,加探测零收益。需要对称的是保证(表存在就不该因建表失败而失能),不是代码;实测结论已钉进sqlite.py的模块 docstring,防止后来的人为对称加回来。遗留一条窄缝备查:表不存在 + 构造瞬间库被排他锁(多进程共库)→ SQLite 侧仍会失能。修它要把 SQLite 也改成 lazy 重试结构,不在本 issue 范围内。
测试
升级
已上传 registry 并
pip download解包验证过。若之前为绕开本问题给应用账号授了CREATE ON SCHEMA,现在可以收回——表存在时库不再需要该权限。日志措辞也细分了:建池失败/建表探测失败(跳过本条,下次重试)/建表失败(表不存在,记录无处可落)。决策全文见
research-wiki/designs/issue9-telemetry-ddl-probe.md(含被否决的四个备选及理由)。