A free-plan database was suspended at its monthly transfer cap on 10/16/26. No alert fired before it. The suspension itself was the first sign.

What had been happening

A sync job ran every 15 minutes and appended history rows. When nothing had changed, it appended them anyway. That is 58,368 identical rows a day from one job. In total it had written 940,017 history rows for 609 events, and only 620 of those rows were real changes.

For the billing period running October 1 to November 1, the project had moved 5,567,252,675 bytes of data. The free plan allows 5 GB of transfer a month. The cap suspends the database when it is passed, and it was passed on the 16th.

The move

The same day, the team created a new project on the free plan and restored it from the nightly dump taken 10/15/26 at 4:30 am. The restore did not carry the junk history rows over, on purpose. Writes made in the old project after the dump point did not carry over. The old project stayed suspended as a fallback.

The free plan keeps a 6 hour point-in-time recovery window and no snapshots. Past six hours, the nightly dump is the only protection, so the restore was only as good as that file.

The fix

The writer now writes only when a value has changed. A sync run that finds nothing new writes nothing. Four other pieces went in the same day:

  • A nightly prune job that removes old low-value rows on a schedule and skips the financial tables.
  • A one-time cleanup script for the identical rows.
  • A watch on transfer and database size that alerts at 50% and 80% of the 5 GB cap, and on growth over 20 MB a day.
  • A single backup path. The old backup job on a personal machine was retired.

The next day the fee and allocation writers got the same treatment: write on change, read in batches. The commit says it removed 8,616 no-op updates per cycle. The nightly dump fell from about 46 MB to about 3.3 MB after the cleanup.

What I got wrong

I was watching the wrong thing. The cap was not what scared me most. The database was down, completely, and I did not know. I only found out because Neon emailed me.

That is the part I own. What was needed was better monitoring, and it had to be smart monitoring, the kind that does not chew up any of my resources.

Ironically, that loop is what caused the next problem: Our Own Monitoring Kept a Serverless Database Awake and Billing.

What the record does not say

The records state the decision to duplicate the project onto a fresh free plan project and restore from the last good dump. They do not say which other options were considered or why each was set aside, so this post does not claim any.

What to take from it

A job that rewrites identical rows on a timer can use up a free plan’s whole monthly transfer allowance inside one billing period. No alert fired before the cap, and the cap goes off by suspending the database.

The writer that did this wrote 940,017 history rows, and only 620 of them were real changes. It now writes nothing when nothing has changed, and the alert fires at 50% of the 5 GB cap, not after it.