提供案例
billflare.

原帖报告已收录: 金额与进展来自原帖及关联回复。

D1rentierdigital-d1-dev-poller-reads

Rentier Digital:零用户开发应用的 D1 用量像正式流量一样计费。

Phil(rentierdigital)称备案监控应用仍在开发、零用户,但一天 D1 读取约 4700 万行(日均约 1290 万,对免费档每日 500 万上限;7 月 2 日峰值 2200 万)。他将费用归因于后台轮询器每次扫描 18000 行、451 次中 447 次空结果,随后迁离 D1。未给出美元发票金额。

发生了什么

报告原因
重复轮询
事件日期
日期未记录
首次报告
2026-08-10 15:15 UTC
账单处置
尚无结果记录
案例进展
尚未确定
Cloudflare 公开回复
尚未收录

最新报告进展

作者在修复轮询器后从 D1 迁到本地 SQLite 再迁到 Postgres;未报告退款、抵扣或发票美元金额。

查看时间线中的最新进展

原始来源

每项来源链接对应的报告或回复。

案例时间线

  1. 作者发布 D1 计费经过

    到 𝕏 阅读

    作者在 𝕏 文章中称,零用户的备案监控开发应用一天 D1 读取约 4700 万行(日均约 1290 万,对免费档每日 500 万上限;7 月 2 日峰值 2200 万),主因是后台轮询器每次扫描 18000 行、451 次中 447 次空结果。

    Phil | Rentier Digital Automation

    @rentierdigital · 𝕏 原帖

    switched databases twice in 10 days. the real problem wasn't the engine. it was a background poller scanning 18k rows per tick and coming back empty 447 out of 451 times. fixed that, then fixed the queries. the database was never the bottleneck. https://t.co/ToXGmFAdux
    阅读全文归档于

    Phil | Rentier Digital Automation · @rentierdigital

    I Switched Databases Twice in 10 Days. The Engine Was Never the Problem.

    47 million rows read in a day. Zero users. The app was still in dev, nobody had logged in outside of me, and the Cloudflare D1 dashboard was showing traffic numbers that belonged to a mid-size SaaS with actual customers. I run a monitoring tool for company filings, the kind of B2B thing where lawyers and analysts check if a director changed or a subsidiary got created. Small scope, small user base, at least for now. So when I finally opened the billing tab that morning, the question wasn't "is Postgres better than SQLite." It was simpler and dumber: can switching database engines actually fix a speed problem, or does it just move the problem somewhere you can't see it as easily. 10 days and 2 migrations later, I have an answer, and it costs more to admit than it does in dollars. The Double Trap of a Managed Engine Two separate problems were hiding behind that 47 million number, not a single one. The first was pure billing math. D1's free tier caps you at 5 million rows read per day. I was averaging 12.9 million, with a spike to 22 million on July 2. A background poller was the culprit, scanning 18,000 rows per tick to check for new filings, and coming back empty in 447 out of 451 runs. Almost all cost, almost no signal. The second problem had nothing to do with the bill. It was a hard engine limit that only showed up in production: "LIKE pattern too complex," a flat 500 error, triggered by a director's name that happened to be 59 characters long. I couldn't reproduce it locally. bun:sqlite, the engine I was testing against on my machine, doesn't enforce that ceiling at all. Same SQL, 2 different verdicts, depending on which box ran it. This is the database version of "works on my machine," except the machine in question was a live production box with actual filings on it. I fixed the poller first, because that's the one with a dollar sign attached. The second bug sat there, waiting, structurally unrelated to any billing decision I could make. Billing catches what you're watching. Production catches what you're not. Migration 1: D1 to Local SQLite July 21. I moved off D1, not off SQLite itself, and that distinction matters more than it sounds like it should. Sam Lambert, who runs PlanetScale (a company that sells managed database hosting, so not exactly biased toward "skip the server"), put it cleanly: a server engine handles network clients, concurrent writes, durable storage under load. An embedded engine runs in-process, no network hop, optimized for simplicity and a small footprint. D1 is SQLite wrapped in a managed, billed, network-facing layer. Running SQLite locally strips that layer off entirely. The difference showed up immediately. Queries that used to round-trip through Cloudflare's edge came back sub-millisecond. /login went from whatever it was before (I genuinely didn't have visibility into that, which is its own small embarrassment) to 110 to 160ms through the tunnel. Same query engine, functionally. Zero cost. Every D1 quota limit, gone, because there was no quota anymore. For about a week, this looked like the fix. The Wall at 2.12 Million Companies Then the dataset grew to 2.12 million companies, and a different wall showed up. Not a billing wall this time. A capacity one. The working set (roughly 9.7GB of heap plus 8.1GB of index) didn't fit in the VPS's 7GB of RAM anymore. /admin/national, the page that aggregates across every company in the database, took 12.07 seconds cold. HAProxy's timeout sits at 50 seconds, so it wasn't dying, not yet, but the same page could swing by a factor of up to 300x depending on whether the cache happened to be warm. That's not a performance profile, that's a coin flip with expensive tails. And here's the part that actually scared me more than the RAM math: I had no way to find out why. No serious read/write concurrency handling to reason about, no GIN trigram indexes, no EXPLAIN ANALYZE that told me anything useful. SQLite's query planner gives you output, technically, but it wasn't built to diagnose this kind of cross-table aggregation at this scale. The VPS was still up, in the sense that HAL still had the pod bay doors closed. Technically true. Deeply ominous. (Unrelated, except that it wasn't: this is also the week my kid decided 4am was a completely normal time to ask about dinosaurs. By the time I actually sat down with the VPS logs I'd already misread 3 timestamps as UTC when they were local, twice.) 🤦 This VPS had already choked once before, on a Docker version bump that broke 2 things I didn't even know were dependent on each other until Claude Code dug through the logs and found them. I got past the wall that time. Barely. Nothing said it wouldn't come back at the next scale-up, and next time the engine wouldn't have anywhere simple left to run. Migration 2: SQLite to Postgres July 29, 00:11, forward-only, no rollback plan because I was tired and confident in roughly equal measure. This switch wasn't about chasing raw speed. It was the national rollout forcing my hand: the numbers I'd just seen at 2.12 million companies told me the next scale jump would break something I couldn't even diagnose, let alone fix. The numbers right after the switch looked clean. /api/decouvertes at 58 to 93ms. Aggregate endpoints at 9 to 45ms. Pool wait time at p95: 2.2ms. I want to be honest about what these numbers actually meant at that moment, because it's tempting to read them as "Postgres won." They meant I finally had EXPLAIN ANALYZE that told the truth, connection pooling that behaved predictably, and index types SQLite never offered at this scale. That's tooling, not magic. Whether the magic was ever going to show up on its own is a different question, and at this exact point in the story, I didn't have the answer yet. The Queries Were Still Bad Here's the part that actually mattered: on Postgres, the queries were still bad. All of them. Until I went in and fixed each one by hand. Full-text search went from 2141ms to 21ms, and the fix wasn't the engine, it was a GIN trigram index paired with a bounded subquery. /api/dirigeants sorted by company count went from 468 separate database queries down to 11, which took response time from a range of 5 to 30 seconds (sometimes flat 500s) down to 0.5 to 2 seconds. A breadth-first search across subsidiary relationships dropped from 199 queries to 18. A dirigeants bucket endpoint went from 709ms to 50ms once I stopped it from doing 7,404 disk reads per call, down to 149. Index cleanup shrank a table from 8,168MB to 5,890MB. And then there was the query that humbled me the hardest: a search filter with 1,282 chained POSITION clauses running against 161,000 rows, which is roughly O(rows x clauses) if you want the ugly math spelled out. It crossed Postgres's 30-second statement timeout exactly the way it used to scan-and-choke on D1. Same wall, different engine, my own personal "YOU DIED" screen showing up twice in the same boss fight. Chris Munns, who leads migration work at PlanetScale (so, again, someone with zero incentive to defend SQLite), says 90% of the painful Postgres migrations he sees come from queue-style workloads pushed onto Postgres at scale, constant contention, autovacuum fighting the write load, wrong tool for the job nearly always. Worth sitting with that, because it cuts the other way too. Postgres isn't the universal answer any more than D1 was the universal mistake. This is the same discipline I wrote about after ditching vibe coding for a stricter process, except this time the receipts were query plans instead of commit diffs. A slow query doesn't get faster because the logo changed. The Question That Replaced It So no. Switching engines never fixed a single slow query. The queries got fixed because I finally had an engine that let me see them clearly enough to fix them by hand, one by one, over days. The price for reaching that sentence: 2 migrations in 10 days, a D1 bill that made no sense for an app with no users, a statement timeout crossed on 2 engines by the exact same broken query. I think that's a fair trade, though honestly I'm not fully sure I'd have made the same call if I'd known the RAM wall was coming a week earlier and could have skipped straight to Postgres. Ingestion still runs out of a single region. The national rollout, the real one, 4 million companies across something like 100 regions, hasn't happened yet. Whether that wall shows up again, and on which engine this time, I don't know. Sources Sam Lambert (@samlambert), on the distinction between server databases and embedded engines Chris Munns (@chrismunns), on why most painful Postgres migrations are workload mismatches, not engine failures This post may contain affiliate links. If you click them, I might earn a small commission, costs you nothing, and helps me keep shipping quality articles every day for your reading pleasure. When you switch databases, you're often just moving the problem somewhere harder to see. This one covers the production visibility gaps that turned a billing issue into a capacity crisis, and why the demo vs product checklist would have caught both before they hit. → Get the welcome kit
  2. 作者重申轮询器是计费主因

    到 𝕏 阅读

    作者称 D1 计费让查询问题变得可见:后台轮询纯属浪费;离开 D1 后还遇到仅生产环境出现的 LIKE 模式限制与内存容量墙。

    Phil | Rentier Digital Automation

    @rentierdigital · 𝕏 原帖

    spoiler: your database wasn't the villain. you were i switched from D1 to local SQLite to Postgres in 10 days chasing speed. turns out i was solving three different problems at once and blaming the engine for all of them the real culprit was a background poller scanning 18,000 rows per tick and coming back empty 447 out of 451 times. pure waste that's not a database problem, that's a query problem. D1's billing just made it visible then there was a LIKE pattern that worked locally in bun:sqlite but threw a 500 in production D1. same SQL, two different verdicts this is the database version of "works on my machine" except the machine was live the third one was real: at 2.12 million companies, the working set didn't fit in 7GB of RAM anymore. that's a capacity wall, not an engine problem but by then i'd already migrated twice bc i kept treating symptoms instead of reading the logs the lesson isn't that Postgres is better. it's that you can't blame the database for what your queries are doing to it i build and ship daily. Claude Code, Codex, whatever ships fastest. SaaS, tools, automations. ⭐ if AI can build it, i've probably broken it first. what works → link in bio
  3. 作者总结迁离 D1

    到 𝕏 阅读

    作者再次报告零用户一天读取 4700 万行、D1 像有客户一样计费,并描述迁到本地 SQLite 再迁到 Postgres、手工修查询。未给出发票美元金额。

    Phil | Rentier Digital Automation

    @rentierdigital · 𝕏 原帖

    i switched databases twice in 10 days and learned the hardest lesson: the engine was never the problem. 47 million rows read in a day. zero users. my filing monitor app was still in dev, but cloudflare d1 was billing me like i had customers. so i migrated to local sqlite. queries got faster. problem solved, right? nah. then the dataset hit 2.12 million companies and everything broke differently. the vps ran out of ram. /admin/national took 12 seconds cold, then 300x slower depending on cache state. a coin flip with expensive tails. so i migrated again, this time to postgres. the queries were still bad. all of them. until i went in and fixed each one by hand. full-text search went from 2141ms to 21ms. not because postgres is magic. because i finally had explain analyze that told the truth, and i built a gin trigram index paired with a bounded subquery. /api/dirigeants went from 468 separate database queries down to 11. the real problem was never d1 vs sqlite vs postgres. it was that i was writing bad queries and couldn't see it until the engine gave me tools to diagnose them. you can hide a lot of sins behind a managed layer or an embedded engine. postgres just made the sins visible. switch engines if you need to. but switching engines doesn't fix bad queries. it just moves the problem to a place where you can finally see it. i build and ship daily. Claude Code, Codex, whatever ships fastest. SaaS, tools, automations. ⭐ if AI can build it, i've probably broken it first. what works → link in bio
官方说明账号级用量计费产品

预算预警

Cloudflare 官方说明账号级费用预警及其通知边界。

D1

失控的客户端同步循环使 D1 可计费读取 6195.2 亿行,超额 $619.52。

社区用户 hoagiaytim.hp 称自身代码缺陷使客户端同步在 2026 年 7 月 16 日至 23 日每轮都重新读取全部数据,30 天内读取 6445.2 亿 D1 行,其中 6195.2 亿行可计费,而数据库总量仅约 103 MB。作者称就 $619.52 读取超额开的支持工单 20 天、五次跟进后仍未分配处理人。

作者经验D1 +1

测试 D1 时留意每日行读写额度

Michal Raczka(@michalraczka)提醒:D1 每日行读写额度用尽后,到 UTC 午夜前都不能再查询;索引用额外写入换取更少读取;大量读写的测试改在本地 Miniflare 跑。