5 million rows read. That's the daily free limit for SQLite-backed Durable Objects, and on 11-07 we went past it

The limit is per account. That's the part I hadn't really thought about

What burned through it was one alarm. We run a small multiplayer pixel game called Dothyeolmaeng, and one of its Durable Objects sets an alarm that fires every 5 seconds. Every time it fired it ran a query on a table with no index on the column it filtered by, so SQLite scanned the whole table. Every 5 seconds, all day.

On its own that's just a slow query. The bad part is that the same Cloudflare account also runs our error collector and the server that handles friend invites. When the alarm used up the shared read budget, both of them stopped too. So the thing that was supposed to tell me something broke was also broken

The fix was mostly boring. I added two indexes so the alarm reads the rows it needs and skips the rest of the table. The alarm was also reading the bot list from the table on every tick, and that list barely changes, so now it's cached in memory and only reloaded when it actually changes.

Then I added a watcher that checks usage every hour, so the next time something like this starts climbing I see it the same day and not when everything goes down

If you're on DO SQLite, here's what you can check in five minutes. The cursor from sql.exec has a rowsRead field. Log it next to the query:

const cur = this.ctx.storage.sql.exec(query, ...args); const rows = cur.toArray(); console.log(query, cur.rowsRead); 

Read it after you've gone through the rows, since it fills in as the cursor is read. Then look for any query where rowsRead is way bigger than the number of rows you actually got back. That gap is the full table scan. Ours read the whole table to get back a handful of rows, on a 5 second loop

I'd been staring at request counts on the dashboard and they looked completely fine. Requests were never the issue, rows were

https://dumbgamelab.com