{"post":{"slug":"sequence-exhaustion-2026","title":"Sequence Exhaustion: The Outage With a Countdown","excerpt":"A 32-bit identifier column runs out at around two billion rows. It is entirely predictable, entirely preventable, and still takes systems down every year.","category":"code","date":"January 3, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">A</span> 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.\n\nIt 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.\n\n## What changed in 2026\n\n- **64-bit defaults became standard.** New schemas overwhelmingly use large integer identifiers, which removes the problem going forward.\n- **Legacy tables kept arriving at the limit.** Systems designed years ago with 32-bit keys continued to hit it.\n- **Online migration tooling improved.** Changing a column type without extended locking became more achievable, though still substantial work.\n- **Monitoring became standard practice.** Sequence headroom moved onto database dashboards rather than being discovered at exhaustion.\n\n## Usage runs ahead of row count\n\nThe reason teams are surprised: **sequence values are consumed whether or not a row survives.**\n\nA 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:\n\n- The insert fails a constraint.\n- The transaction rolls back.\n- The row is later deleted.\n- An upsert conflicts and updates instead of inserting.\n\nA 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](/blog/upsert-patterns-explained-2026), which is a common accelerator.\n\nBatch operations that reserve blocks of values, and caching in connection poolers, add further gaps.\n\n## Monitor headroom, not row count\n\nThe right metric is what fraction of the sequence's range is consumed, checked periodically and alerted on well in advance.\n\n| Headroom remaining | Action |\n| --- | --- |\n| Above 50% | Nothing |\n| 20–50% | Plan the migration |\n| 10–20% | Schedule it |\n| Below 10% | Execute now |\n| Below 1% | Emergency |\n\nThe 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.\n\nEvery foreign key referencing the column needs the same change, so the migration spans tables. Discovering that at 99% consumption is unpleasant.\n\n## Migrating without an outage\n\nThe pattern is the same expand-and-contract shape used for any breaking schema change — see [expand and contract migrations](/blog/expand-contract-migrations-2026).\n\nAdd 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](/blog/mvcc-explained-2026). Have the application write both columns. Migrate foreign keys and reads to the new column. Then swap and drop the old one.\n\nSlow, 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.\n\nThe 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.\n\n## Common mistakes\n\n- **32-bit identifiers on anything that might grow.** The storage saving is trivial; the migration is not.\n- **Monitoring row count instead of sequence value.** They diverge, sometimes greatly.\n- **Alerting at 95% consumption.** Too late to migrate calmly.\n- **Direct type change on a large table.** Extended lock, blocked writes.\n- **Backfilling in one transaction.** Long transaction, blocked cleanup.\n- **Forgetting referencing foreign keys.** They need the same change.\n- **Assuming deletes free values.** They do not.\n\n## FAQ\n\n**How long until a 64-bit sequence exhausts?**\nEffectively never at any realistic rate. This is a solved problem once you have made the change.\n\n**Can I reset a sequence to reuse gaps?**\nOnly if you are certain no historical value is still referenced anywhere, including in archives, logs, and external systems. Almost always a bad idea.\n\n**What if I use UUIDs?**\nNo sequence, no exhaustion — different tradeoffs entirely, per [UUID vs bigint keys](/blog/uuid-vs-bigint-keys-2026).\n\n**How do I check current consumption?**\nQuery the sequence's current value and compare against the column type's maximum. Automate it as a monitored metric rather than a manual check.\n\n## Where to go next\n\nFor migrating the column safely, read [expand and contract migrations](/blog/expand-contract-migrations-2026). For the key-type decision, [UUID vs bigint keys](/blog/uuid-vs-bigint-keys-2026), and for what consumes values unexpectedly, [upsert patterns](/blog/upsert-patterns-explained-2026).","hue":304,"metaTitle":"Sequence Exhaustion: The Outage With a Countdown","metaDescription":"Why integer sequences run out sooner than row counts suggest, how to monitor headroom, and why migrating the column type is painful under pressure.","keywords":["sequence exhaustion","integer overflow primary key","int4 to int8 migration","sequence monitoring","identity column limits","database capacity planning","id exhaustion outage","database operations 2026"],"tldr":["**32-bit integers exhaust around two billion** — sooner than teams expect, because gaps consume values too.","**Failed inserts and rollbacks still consume sequence values** — usage runs ahead of row count.","**Migrating the type on a large table is slow and locking** — which is why this becomes an emergency.","**Monitor headroom as a percentage** — the alert should fire months out, not days.","**Skip:** 32-bit identifiers on anything that could grow. The storage saving is not worth the migration."],"readTime":"4 min read","wordCount":795},"related":[{"slug":"lock-timeouts-explained-2026","title":"Lock Timeouts: Failing Fast Instead of Queueing Forever","excerpt":"A migration waiting for a lock does not just wait. Everything arriving behind it queues too, and a brief schema change becomes a full outage.","category":"code","date":"January 4, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":300},{"slug":"expand-contract-migrations-2026","title":"Expand and Contract: Schema Changes Without Downtime","excerpt":"Renaming a column in one deploy breaks every running instance of the old code. Splitting the change into three deploys makes it invisible to users.","category":"code","date":"August 13, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":320},{"slug":"how-to-write-a-database-migration-2026","title":"How to write a database migration in 2026: safe, reversible, and zero-downtime","excerpt":"Database migrations fail when they lock tables in production or cannot be rolled back. This guide covers the 2026 patterns for safe, zero-downtime schema changes in Postgres.","category":"code","date":"April 6, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":305}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"llm-latency-budgets-2026","title":"LLM Latency Budgets: Allocating the Time You Have","excerpt":"A two-second target is not a model requirement. It is a budget to divide across retrieval, prefill, generation, and everything else — and the model is often not the largest line.","category":"ai","date":"January 3, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":197},"older":{"slug":"best-401k-providers-small-business-2026","title":"Best 401(k) providers for small business owners in 2026","excerpt":"Tested 2026 ranking of 401(k) providers for small business — by fees, fund quality, and which ones actually serve sub-50-employee plans.","category":"finance","date":"January 2, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":38}}