A 32-bit signed integer holds values up to about 2.1 billion. A table using one for its identifier stops accepting inserts when the sequence reaches that, and the failure is abrupt: writes start erroring, and there is no graceful degradation.
It is among the most predictable outages available. The countdown is visible in a system view, the arrival date is calculable, and it still catches teams because nobody was looking.
What changed in 2026
- 64-bit defaults became standard. New schemas overwhelmingly use large integer identifiers, which removes the problem going forward.
- Legacy tables kept arriving at the limit. Systems designed years ago with 32-bit keys continued to hit it.
- Online migration tooling improved. Changing a column type without extended locking became more achievable, though still substantial work.
- Monitoring became standard practice. Sequence headroom moved onto database dashboards rather than being discovered at exhaustion.
Usage runs ahead of row count
The reason teams are surprised: sequence values are consumed whether or not a row survives.
A sequence hands out values without regard to transaction outcome, by design — it must not block concurrent inserts waiting to see whether one commits. So a value is consumed even if:
- The insert fails a constraint.
- The transaction rolls back.
- The row is later deleted.
- An upsert conflicts and updates instead of inserting.
A table with high conflict rates, frequent rollbacks, or heavy upsert usage burns through values considerably faster than its row count suggests. A table with a hundred million rows may have consumed several times that many values — see upsert patterns, which is a common accelerator.
Batch operations that reserve blocks of values, and caching in connection poolers, add further gaps.
Monitor headroom, not row count
The right metric is what fraction of the sequence's range is consumed, checked periodically and alerted on well in advance.
| Headroom remaining |
Action |
| Above 50% |
Nothing |
| 20–50% |
Plan the migration |
| 10–20% |
Schedule it |
| Below 10% |
Execute now |
| Below 1% |
Emergency |
The reason the alert must fire early is that the fix is slow. Changing a column from 32-bit to 64-bit rewrites every row, and on a large table that takes hours and holds locks that block writes. Under emergency conditions, with writes already failing, that is the worst possible time to attempt it.
Every foreign key referencing the column needs the same change, so the migration spans tables. Discovering that at 99% consumption is unpleasant.
Migrating without an outage
The pattern is the same expand-and-contract shape used for any breaking schema change — see expand and contract migrations.
Add a new 64-bit column. Backfill it in batches, keeping transactions short so cleanup is not blocked — a single enormous update is its own problem, per MVCC explained. Have the application write both columns. Migrate foreign keys and reads to the new column. Then swap and drop the old one.
Slow, tedious, and it avoids the locking that a direct type change causes. It also requires the headroom to do it gradually, which is the argument for early monitoring.
The shortcut worth knowing: if the column is signed and only positive values have been used, half the range is technically available by allowing negatives. Ugly, and it has bought teams the weeks needed to do the real migration properly.
Common mistakes
- 32-bit identifiers on anything that might grow. The storage saving is trivial; the migration is not.
- Monitoring row count instead of sequence value. They diverge, sometimes greatly.
- Alerting at 95% consumption. Too late to migrate calmly.
- Direct type change on a large table. Extended lock, blocked writes.
- Backfilling in one transaction. Long transaction, blocked cleanup.
- Forgetting referencing foreign keys. They need the same change.
- Assuming deletes free values. They do not.
FAQ
How long until a 64-bit sequence exhausts?
Effectively never at any realistic rate. This is a solved problem once you have made the change.
Can I reset a sequence to reuse gaps?
Only if you are certain no historical value is still referenced anywhere, including in archives, logs, and external systems. Almost always a bad idea.
What if I use UUIDs?
No sequence, no exhaustion — different tradeoffs entirely, per UUID vs bigint keys.
How do I check current consumption?
Query the sequence's current value and compare against the column type's maximum. Automate it as a monitored metric rather than a manual check.
Where to go next
For migrating the column safely, read expand and contract migrations. For the key-type decision, UUID vs bigint keys, and for what consumes values unexpectedly, upsert patterns.