D1 的开发体验确实非常好:自动创建本地 SQLite、在几毫秒内响应的查询、无需配置服务器、无需管理连接字符串。这种发展中的零摩擦往往会造成一种错觉,即银行在生产中也会采取同样的行为。不会的。每个银行 2GB 上限、累加的子请求、UPDATE 写入的行成本以及每个会话需要手动选择加入的外键强制执行是生产中出现的四个限制,而快速入门从未提及。
没人计划的2GB上限
D1 对每个 [数据库10 施加 2GB 的限制。这个数字是固定的——无法通过购买额外容量来增加特定银行的数字。付费计划允许最多 10 个 D1 银行,这意味着理论上在不同实例之间分配的总容量为 20GB。
对于数据量适中的简单 CRUD 应用程序来说,2GB 足够使用数年。对于具有日志、事件历史记录、用户数据上传或随使用量增长的表的应用程序,限制出现的时间比预期要早。问题不在于达到 2GB 本身,而在于您意识到数据即将到达的那一刻,数据正在生产中并且没有定义的分区策略。
对于具有可预测增长的应用程序来说,最干净的路径是从一开始就按域对数据进行分区:一个数据库用于活动事务数据,另一个数据库用于历史记录,另一个数据库用于日志。 SaaS 应用程序可以按租户范围进行分区 - 银行 A 中的租户为 1 到 1000,银行 B 中的租户为 1001 到 2000。Worker 根据租户 ID 决定访问哪家银行,而用户不会注意到。这种架构需要在第一个数据到达之前就考虑好,因为用接近极限的生产数据库重构分区是一项微妙的操作。
子请求的隐藏成本
D1 中执行的每个查询都算作 Worker 预算中的子请求。 Workers 的限制是每次调用 1000 个子请求。这看起来很宽敞,直到您弄清楚单个 HTTP 请求的作用:验证令牌(1 个查询)、加载用户(1 个查询)、检查权限(1 个查询)、获取资源列表(1 个查询)等等。一个端点上有十个查询是很常见的。
N+1 问题很快就改变了这个数字。列出 50 个订单,然后从每个订单中单独获取商品的端点会执行 1 + 50 = 51 个查询。结合缓存的 5 KV 读取和元数据的 3 R2 访问,此单个请求使用 59 个子请求。仍在限制之内,但对于更复杂的端点来说,余量很小。
1 无需更改数据结构即可解决 N+1 问题:将多个查询分组到单个调用中,并且它们全部在单个子请求中执行。结果以数组形式返回,每个查询包含一个元素。对于订单和商品模式,使用动态构建的查询的 2 可以将 51 个子请求减少到 2 个 - 一个针对订单的查询,一个针对所有商品的 3 查询。
写入放大:每百万行写入 1 美元的真正含义
写入的 D1 计费模型是按受影响的行计算的,而不是按操作计算的。修改 5,000 行的 4 会花费 5,000 次写入,无论是否是对银行的单次调用。按照每百万行写入 1 美元计算,此 UPDATE 每次执行的成本为 0.005 美元。
这个数字看似很小,但批量操作却有累积效应。作为夜间处理的一部分,每天更新 100,000 条记录的状态的作业每次运行费用为 0.10 美元,仅该作业每月费用为 3 美元。乘以几个类似的作业,写入的成本很容易超过读取的成本。
免费套餐每天写入的行数限制为 10 万行。单个批量更新操作可能会消耗整个限制。这意味着免费层与执行批量更新的处理管道不兼容 - 对于任何具有批量写入的工作负载,付费计划是唯一的选择。
降低写入成本的模式:仅追加而不是更新(插入新的状态记录而不是更新现有状态记录)、定期压缩而不是连续更新、以较低的频率进行大批量处理而不是细粒度和频繁的更新。
外键和 PRAGMA:每个会话的技巧
默认情况下,SQLite 不强制执行外键。此行为由 D1 继承,无需修改。如果您在模式中定义 5 并插入表 7 中不存在的 6 行,则 D1 会接受 INSERT 而不会出现错误 — 除非您在该会话中运行 8 。
关键细节是“在那次会议中”。 PRAGMA 在连接之间不持久。每个需要外键强制执行的 Worker 调用都需要执行 PRAGMA 作为第一个操作。如果您的代码通过助手初始化数据库,请在其中添加 PRAGMA - 并记录这一点,因为它最终会逃避团队中某人的注意。
不这样做的代价是无声的:您积累了孤立的记录,而日志中没有任何错误。发现问题意味着手动搜索损坏的引用,修复问题意味着决定是删除无效记录还是创建丢失的父记录。如果损坏的数据量很大,那么纠正就变成了向生产环境的微妙迁移。
始终在任何其他操作之前运行 PRAGMA 的启动助手是解决此问题的最简单的投资。查询 9 花费的时间不到一毫秒。在生产中调试无效数据的时间要长得多。
另请阅读
- [生产中的耐用物品:法案将是什么样子以及令人惊讶的限制11
- [生产中的 Cloudflare Workers:hello world 之后发生了什么变化12
- [生产中的 KV:有效的模式和一开始就具有欺骗性的模式13
- [工人:CPU 和内存限制 - 文档没有很好地解释14
- [Cloudflare D1:边缘的 SQLite 数据库 — 以及为什么“边缘”的含义并不像看上去的那样15
- 【D1查询慢:如何诊断和优化16
