Right after a routine Postgres container swap, a quick health check showed zero live rows in three of the database's most important tables. For a moment, that looked exactly like catastrophic data loss. It wasn't — and the difference between panicking and checking came down to running one more, cheaper query before believing the first one.
The number that looked like disaster
Postgres keeps a running estimate of how many live rows each table holds, exposed through its own statistics views — a number applications and administrators routinely check as a fast, cheap proxy for "does this table have data in it." Immediately after this particular container swap, that estimate read exactly zero for three of the corpus's core tables — the kind of tables that should always hold millions of rows in a live, working system.
Why zero is exactly the number that triggers panic
A live row count of zero doesn't read as "slightly off" the way a wrong-but-plausible number might. It reads as "everything is gone." On a database that had been running fine moments before a planned, routine change, that's the worst-case interpretation landing immediately, with no ambiguity in how to read the number at face value.
The cheap check that settled it
Rather than trusting that number or escalating based on it, a direct query ran against one of the affected tables — a plain select of a few real rows, ordered to pull the most recent ones by id. It returned exactly what a healthy table should: real rows, with a maximum id in the hundreds of millions, consistent with a corpus that had been growing steadily for a long time. The data had never moved. Nothing had been deleted, truncated, or lost in the container swap at all.
What was actually wrong, and why it wasn't a real problem
The row-count estimate Postgres exposes isn't derived by counting rows directly — it's maintained by the database's own internal statistics collector, updated incrementally as normal activity runs. That specific container swap didn't carry the collector's accumulated state over cleanly, so the estimate simply hadn't been populated yet for these tables, independent of anything about the actual stored data. A subsequent statistics refresh, run anyway as part of other maintenance already planned around the same change, resolved it completely. The zero was a bookkeeping gap in a monitoring number, not a description of what was actually on disk.
The one habit that made the difference
The entire incident resolved in the time it took to run one extra, cheap query. The number that looked alarming was an estimate, explicitly documented by Postgres itself as an estimate rather than a guarantee, and estimates can legitimately read zero under specific circumstances — like certain kinds of restarts or image changes — without that meaning what it appears to mean at first glance. Verifying a scary number against a real, minimal, targeted check before treating it as confirmed is a small habit, and it's the entire difference between a one-line footnote and a full incident response over data that was never actually at risk.