Originally published at nlqdb.com/blog

A row in our databases registry has a last_queried_at column. Two unrelated systems read it: a daily sweep that evicts anonymous DBs whose last_queried_at is older than 90 days, and a funnel metric that counts "DBs that have ever returned an answer." Both quietly broke for the same reason, and the bug is worth sharing because it's a whole class of mistake, not a one-off.

We added the column in a migration that backfilled existing rows (UPDATE … SET last_queried_at = updated_at WHERE last_queried_at IS NULL) — textbook. What we forgot: the INSERT on the create path never set the column. So every row created after the migration was NULL.

Now watch both readers fail, differently:

The sweep silently keeps everything. WHERE last_queried_at < :cutoff looks like it evicts old rows. But in SQL, NULL < anything is NULL, which is not TRUE, so a NULL row never matches a < predicate. The age-based eviction became a no-op for every new row. No error, no log — the table just grows.