Metering looks like a counter. Increment on request, read the counter at the end of the month, send an invoice.
It stops looking like a counter the first time a run fails after fetching 40,000 rows and writing none of them.
The question that decides everything
Did the customer consume 40,000 rows?
They fetched them. They did not receive them. Whichever answer you pick, you have to pick it once and apply it everywhere, because the alternative is that support and billing and the dashboard each have their own answer.
Our answer: metering commits with the rows. If the write did not land, it was not consumed. The bandwidth was ours to eat, and eating it is cheaper than arguing about it.
Why not eventual consistency
Because a counter that is eventually right is a counter that is presently wrong, and “presently” is when someone looks at it.
The quota check reads the same number the invoice will. If those can diverge — even briefly — you get the worst possible failure: a customer blocked at their limit who is not actually at their limit, or a customer sailing past a limit that was supposed to stop them.
So metering is in the same transaction as the write. It is slower than an in-memory counter flushed on an interval. It is slower in a way nobody has ever noticed.
The part we got wrong first
We started by metering at the edge, on request. It was simple and it was wrong in exactly the way above: failed runs billed. Moving it next to the write cost a week and removed an entire category of support ticket.
Jonah Reyes
Engineering Manager