@clementvial

Developer from Canada 🇨🇦
Product, infrastructure, AI, and web3.
Mostly on AWS and Cloudflare.

All notes

A Missing Index on D1 Is Now an Outage, Not a Slow Query

Since September 1, D1 queries on the Workers Free plan fail once the account passes its daily row limits:

Your account has exceeded D1's free tier daily row read limit.
Upgrade to a paid plan or wait until tomorrow (midnight UTC) to continue.

Stored data is untouched and the database comes back at midnight UTC. Until then, every query returns that string instead of rows.

Rows read means rows scanned

The allowance is 5 million rows read per day, and read counts what the query had to scan, not what it gave back. A lookup on an unindexed column walks the whole table to find one row:

const { meta } = await env.DB.prepare(
'SELECT * FROM users WHERE email = ?'
).bind(email).all();
meta.rows_read; // 5000

Five thousand users, one row returned, five thousand counted against the limit. A thousand logins in a day and the account is done, on a table small enough that the query still answers in under a millisecond. The latency never tells you.

Every result carries meta.rows_read, so the expensive queries are findable before the limit finds them.

The fix spends the other budget

An index turns the scan into a few rows:

CREATE INDEX idx_users_email ON users(email);

Writes are capped at 100,000 rows per day, fifty times tighter than reads, and each index counts on its own. Inserting into a table with two indexes is three rows written, not one. A schema with an index on every filtered column can put a seed script or a backfill over the line on its first run.

The read limit is the one that breaks a live site. The write limit is the one you walk into while fixing it.