Datasette 开发者 Simon Willison 在7月14日发布了 1.0a37,发布说明只有一句话:性能和文档改进,外加撤销了一次"cosmetic"的API改动。这个描述很容易被跳过——版本号里的"a"说明还在alpha阶段,谁会指望alpha小版本里有什么大事。
但翻开这次发布背后的issue和PR就会发现,"minor"这个词用得相当保守。权限检查的性能优化是数量级的重构,一次被撤回的API改动暴露了Datasette在API设计与插件生态之间的真实拉锯,还有一个悄悄修复的写事务bug,直接关系到生产环境的数据可靠性。
权限检查从45秒降到16毫秒
Datasette 从 1.0a20 开始把权限系统从基于Python回调的旧模型,换成了基于SQL的actions模型。好处是权限规则能直接用SQL表达,但随着数据库和表的规模变大,权限检查的开销也跟着涨。
issue #2832 里记录的基准测试很直观:4000个资源级别,权限检查耗时从4612毫秒降到8.5毫秒;5000个资源,从超过45秒降到16毫秒;8000个资源,从超过170秒压到32毫秒。按这个曲线推算,10万资源规模下,原本可能要跑数小时的检查,现在预计只要209毫秒。
这不是调了几个参数就能拿到的提升,而是查询架构本身变了——从原来"资源逐个和规则做联结"改成"先把规则聚合好,再统一联结资源"。328项权限测试全部通过,结果和旧实现逐行一致,说明这是纯粹的性能重构,权限语义没有变。
这组数字对普通用户几乎无感,但对在生产环境里跑着大型schema的机构——数千到数万张表、多个数据库挂载的场景——是实打实的可用性问题。权限检查慢到分钟级,页面基本没法用。
一次被打脸的API改动
1.0a36 曾经把 /-/plugins.json 接口从顶层数组格式,改成了 {"ok": true, "plugins": [...]} 的对象封装。这个改动本身没毛病,和Datasette其他API的返回格式更一致。
问题是,这个"更一致"的设计直接打断了几乎所有现有插件的测试套件。插件作者的测试代码普遍假设 /-/plugins.json 返回的是一个数组,可以直接遍历或索引;换成对象封装后,这些代码全部报错。
Simon Willison 在1.0a37里把这个改动原样撤回,恢复成顶层数组。
- 风险.Datasette 插件生态目前的测试覆盖率和API变更容忍度都偏低,任何看似"更规范"的调整都可能大面积破坏下游。
这一进一退很说明问题。Alpha阶段名义上允许破坏性变更,但当变更的代价是"几乎所有插件测试套件崩溃",项目组选择的是认错回滚,而不是要求插件生态跟着适配。
一致性让位于兼容性,是这次发布里最诚实的一次退让。
一个藏起来的事务bug
1.0a36 升级到 sqlite-utils 4.0 之后,出现了一个不容易被发现的回归:sqlite-utils 可能在 Datasette 的写任务真正完成之前,就自行提交了事务。这样一来,如果后续操作失败,本该被整体回滚的写入,反而已经落盘保留下来了。
1.0a37 的修复方式是给 db.execute_write() 加了一个 transaction= 参数,默认为True,可以设为False来支持 VACUUM 之类SQLite不允许在事务内执行的操作;同时写任务改用 BEGIN IMMEDIATE,确保任务失败时相关写入真的会被回滚。
- 结论.依赖 Datasette + sqlite-utils 组合做批量写入的团队,这类底层组件升级带来的静默兼容性问题,比表面的功能改动更值得留意。
谁该盯着接下来的动作
权限系统从1.0a20重构到现在,插件作者面对的迁移成本一直没有降低——旧的 register_permissions/permission_allowed 钩子作者,迟早得迁到新的 register_actions/permission_resources_sql 模型。1.0a37新增的权限调试接口,能解释某次访问被允许或拒绝的具体原因、匹配到哪条规则,这对排查迁移问题有实际帮助,但不改变"必须迁移"这个前提。
对插件开发者来说,现实的判断是:JSON API和权限API目前都还没有进入冻结期,/-/plugins.json 这种改了又撤的情况可能还会发生。在Datasette正式宣布1.0之前,把兼容性测试写扎实,比追每个alpha版本的新特性更划算。
