{"post":{"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","content":"<span class=\"dropcap\">A</span> migration adds a column. It needs a brief exclusive lock, which should take milliseconds.\n\nA long-running query is holding a conflicting lock, so the migration waits. That is fine on its own. What is not fine is that in most databases, lock requests queue — so every subsequent query needing any conflicting lock on that table now waits *behind the migration*, including ordinary reads that would have been served instantly.\n\nWithin seconds the table is effectively unavailable, and the cause is a schema change that has not done anything yet.\n\n## What changed in 2026\n\n- **Lock timeouts before DDL became standard practice.** Migration tooling increasingly sets them automatically.\n- **The queueing behaviour got better understood.** Recognition that a waiting DDL statement blocks readers spread beyond database specialists.\n- **Retry-with-backoff patterns matured.** Automated retry of schema changes became a feature of migration frameworks.\n- **Online DDL support improved.** More operations became possible without long locks, reducing exposure.\n\n## Why queueing turns a wait into an outage\n\nLock requests are generally granted in order. When a request for a strong lock is waiting, later requests for conflicting locks queue behind it rather than jumping ahead — this prevents starvation of the strong request.\n\nThe consequence in this scenario: a `SELECT` that would conflict with the pending exclusive lock waits behind it, even though it does not conflict with the currently-held lock at all.\n\nSo the sequence is: a long query holds a weak lock, DDL requests a strong lock and waits, and everything else piles up behind the DDL. Traffic stops on that table, connections fill, and the application starts failing — all triggered by a schema change that never executed.\n\n## The fix\n\nSet a short lock timeout before the DDL statement.\n\nWith a timeout of a few seconds, the migration attempts to acquire its lock. If it cannot within that window, it fails immediately rather than queueing. The queue never forms, traffic continues, and you retry.\n\nThen retry with backoff. Schema changes usually succeed on a later attempt, when whatever was holding the conflicting lock has finished. Several attempts with a pause between them is far safer than one attempt that waits indefinitely.\n\n| Approach | Outcome when blocked |\n| --- | --- |\n| No lock timeout | Queue forms, table effectively down |\n| Short lock timeout | DDL fails fast, traffic unaffected |\n| Short timeout plus retry | Fails fast, succeeds later |\n| Long lock timeout | Long queue, delayed outage |\n\nA long lock timeout is close to no timeout — the queue still forms, it just clears eventually.\n\n## The other half: what is holding the lock\n\nA lock timeout stops you from causing an outage; it does not get your migration applied. If it fails repeatedly, something is holding a conflicting lock persistently.\n\nThe usual culprits are long-running analytical queries, and idle-in-transaction sessions — an application that opened a transaction, touched the table, and then went off to do something slow. The latter is particularly frustrating because the session is doing nothing at all while blocking your change.\n\nBounding those is the complementary fix, per [statement timeouts](/blog/statement-timeouts-explained-2026). A database with sensible statement and idle-in-transaction timeouts has far fewer persistent lock holders for DDL to collide with.\n\nChecking what holds the lock before retrying is worth the minute it takes — most engines expose current locks and the sessions holding them.\n\n## Common mistakes\n\n- **DDL without a lock timeout on a busy table.** The classic self-inflicted outage.\n- **A long lock timeout.** Delays the outage rather than preventing it.\n- **Retrying immediately without backoff.** Repeatedly forms short queues.\n- **Not investigating persistent blockers.** Retrying forever against an idle transaction.\n- **Assuming a quick operation needs no protection.** The lock *acquisition* is the risk, not the operation.\n- **No idle-in-transaction timeout.** Leaves the most common blocker unbounded.\n\n## FAQ\n\n**How short should the lock timeout be?**\nA few seconds is typical. Short enough that a queue does not build meaningfully, long enough to succeed when the table is momentarily quiet.\n\n**Does this apply to all schema changes?**\nAny operation requiring a strong lock, which is most DDL. Some operations have online variants avoiding long locks — check what your engine offers for the specific change.\n\n**What about the concurrent index build?**\nBuilding an index concurrently avoids the long lock and still needs a brief one at start and finish, so a lock timeout is still worth setting.\n\n**Should application queries have lock timeouts?**\nUsually a short one, so a blocked query fails rather than holding a connection indefinitely. It is a different value from the DDL case.\n\n## Where to go next\n\nFor bounding query duration and idle transactions, read [statement timeouts](/blog/statement-timeouts-explained-2026). For the schema-change pattern that minimises locking, [expand and contract migrations](/blog/expand-contract-migrations-2026), and for cancelling a blocking session, [query cancellation](/blog/query-cancellation-explained-2026).","hue":300,"metaTitle":"Lock Timeouts: Failing Fast Instead of Queueing","metaDescription":"Why a blocked schema change queues all subsequent queries, how a short lock timeout prevents the cascade, and how to retry DDL safely.","keywords":["lock timeout","ddl lock queue","schema change outage","blocked migration","lock contention","failed fast database","alter table locking","database operations 2026"],"tldr":["**A blocked lock request blocks everything behind it** — queueing is the mechanism that turns a wait into an outage.","**Set a short lock timeout before DDL** — fail fast and retry rather than queueing.","**Retry with backoff** — most schema changes succeed on a later attempt when the table is quieter.","**Lock timeout differs from statement timeout** — one bounds waiting, the other bounds running.","**Skip:** running DDL on a busy table without a lock timeout. It is the classic self-inflicted outage."],"readTime":"4 min read","wordCount":812},"related":[{"slug":"statement-timeouts-explained-2026","title":"Statement Timeouts: Stopping a Query Before It Stops You","excerpt":"One query scanning a huge table can hold resources long enough to degrade everything else. A statement timeout turns that into an error instead of an incident.","category":"code","date":"January 21, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":312},{"slug":"what-is-a-database-migration-2026","title":"What is a database migration in 2026? Schema changes, versioned","excerpt":"A database migration is a versioned, repeatable change to a schema, tracked the same way source code is. Here is what that means in practice, and why teams stopped editing production schemas by hand in 2026.","category":"code","date":"July 17, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":150},{"slug":"copy-bulk-loading-2026","title":"Bulk Copy Loading: The Fast Path Into a Table","excerpt":"Databases have a dedicated interface for loading data that bypasses statement parsing entirely. It is dramatically faster than inserts and considerably less forgiving.","category":"code","date":"January 8, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":302}],"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":"index-only-scans-explained-2026","title":"Index-Only Scans: When the Table Is Never Touched","excerpt":"If every column a query needs is in the index, the database can answer without reading the table at all. That is frequently the difference between a slow query and an instant one.","category":"code","date":"January 4, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":303},"older":{"slug":"point-in-time-recovery-2026","title":"Point-in-Time Recovery: Undoing the DELETE You Regret","excerpt":"Replication copies your mistakes instantly. Point-in-time recovery is the only mechanism that lets you go back to just before someone ran a DELETE without a WHERE clause.","category":"code","date":"January 4, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":304}}