@@ -1,4 +1,4 @@
1−use anyhow::anyhow;
1+use anyhow::{anyhow, Context};
22use postgres::{types::ToSql, NoTls, Row};
33
44// DbLocker wraps a regular DbConn, only allowing access using the
@@ -22,7 +22,24 @@ impl DbLocker {
2222 const LOCK_KEY: i64 = 4036779288569897133;
2323
2424 pub fn connect(config: &postgres::Config) -> anyhow::Result<Self> {
25− let pg = config.connect(NoTls)?;
25+ let mut pg = config.connect(NoTls)?;
26+
27+ // When running DDL queries that acquire locks, we risk causing a "lock queue".
28+ // When attempting to acquire a lock, Postgres will wait for any long running queries to complete.
29+ // At the same time, it will block other queries until the lock has been acquired and released.
30+ // This has the bad effect of the long-running query blocking other queries because of us, forming
31+ // a queue of other queries until we release our lock.
32+ //
33+ // We set the lock_timeout setting to avoid this. This puts an upper bound for how long Postgres will
34+ // wait to acquire locks and also the maximum amount of time a long-running query can block other queries.
35+ // We should also add automatic retries to handle these timeouts gracefully.
36+ //
37+ // Reference: https://medium.com/paypal-tech/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680
38+ //
39+ // TODO: Make lock_timeout configurable
40+ pg.simple_query("SET lock_timeout = '1s'")
41+ .context("failed to set lock_timeout")?;
42+
2643 Ok(Self {
2744 client: DbConn::new(pg),
2845 })