Every source behind this page, graded. The blog, paper and talk tiers
are empty because this session's network could not reach those hosts; the ledger shipped
beside this file names what was found but left uncited.
Postmortem
GitLab2024-09
Incident Review: GitLab.com is down
Severity 1, all customers, 20 minutes. A shutdown aimed at the idle new v16 cluster
hit the production v14 cluster: one suffix wrong in a knife ssh role. The only full
outage in three years of upgrade campaigns, and the cutover was weeks away.
Carry forwardBuilding the parallel cluster doubles the targets of every destructive command. Guard the blast radius before the second cluster exists.
gitlab.com/gitlab-com/gl-infra/production/-/work_items/18491
Postmortem
GitLab2025-07
High error rate in transactions_primary of patroni-ci violating SLO
92.52% error rate from transaction rollbacks, attributed to a PostgreSQL bug
triggered by the PG17 upgrade. The version cleared staging and rehearsal; the bug
waited for the production workload.
Carry forwardVersion-specific behaviour appears only under real load. The rollback window and a staffed observation period are the mitigation, not more staging.
gitlab.com/gitlab-com/gl-infra/production/-/work_items/20273
Postmortem
GitLab2026-07
More than one postgres instance in read-write mode for patroni-ci
The split-brain alert, fired falsely because a leftover PG18 node from the aborted
weekend upgrade was still grouped with the live cluster in monitoring.
Carry forwardAn abort is complete when the debris is gone from monitoring, not when traffic is back on the old version.
gitlab.com/gitlab-com/gl-infra/production/-/work_items/22502
Postmortem
GitLab2025-12
Patroni processes down on multiple patroni-main-v17 hosts
Nine of ten nodes of the new-version cluster down during the December upgrade
window, cause recorded as unknown; zero customer impact because the cluster was not
yet serving. The December change request was aborted, the upgrade landed in
February.
Carry forwardPre-cutover failures cost a reschedule, not an outage. That asymmetry is the parallel cluster's whole value; protect it.
gitlab.com/gitlab-com/gl-infra/production/-/work_items/20952
Postmortem
GitLab2026-08
Inactive replication slot retained ~4 TB of WAL (INC-12794 follow-up)
The change request lowering max_slot_wal_keep_size documents the incident: a stalled
slot consumer held ~4 TB of WAL and drove the production primary's disk to ~88%. The
new cap is sized against measured WAL rates and a stall budget of hours.
Carry forwardEvery logical-replication upgrade and CDC pipeline leaves slots behind. Cap them as a fraction of disk and alert on lag well before the cap.
gitlab.com/gitlab-com/gl-infra/production/-/work_items/22734
Case study
GitLab2026-07
Upgrade PostgreSQL to PG18 on CI (both production attempts, aborted)
The full written method: standby cluster on PG18, logical replication, Consul
switchover, reverse replication, 75-hour DDL freeze with the stated reason, three
rollback scenarios keyed to incident timing, and the DDL-only abort rule.
Carry forwardWrite the rollback scenarios into the change plan before the window opens; GitLab's aborts are cheap because the abort path was already written.
gitlab.com/gitlab-com/gl-infra/production/-/work_items/22444
Case study
GitLab2026-08
Mock-up Upgrade PostgreSQL to PG18 on CI Test
After two aborts: a dress rehearsal that moves 50% of production workload to PG18,
measures performance on both versions, and ends by switching 100% back and dropping
replication, by design.
Carry forwardRehearse the rollback as the planned ending of a real traffic test, not as a contingency you hope never to run.
gitlab.com/gitlab-com/gl-infra/production/-/work_items/22720
Case study
GitLab2024-11
Upgrade PostgreSQL to v16 on MAIN cluster
The 2024 main-database upgrade: two majors in one hop (14 to 16) over logical
replication, amcheck on the target for 12+ hours, reverse replication and a
monitored rollback window. The template the later campaigns refined.
Carry forwardThe logical path decouples you from version adjacency: skipping a major costs nothing extra once the campaign machinery exists.
gitlab.com/gitlab-com/gl-infra/production/-/work_items/18747
Case study
GitLab2025-08
Upgrade PostgreSQL to PG17, main database (December attempt, aborted)
Pairs the engine upgrade with Ubuntu 20.04 to 22.04 and budgets Fix corrupt
indexes (caused by the Ubuntu upgrade)
as a plan step; validated beforehand by
dedicated production change requests that rebuild indexes under logically replicated
production workload.
Carry forwardIf the OS moves with the engine, index corruption is a planned work item, not a risk. Budget the reindex.
gitlab.com/gitlab-com/gl-infra/production/-/work_items/20440
Source
PostgreSQL2025-02
Commit 1fd1bd871: Transfer statistics during pg_upgrade
PostgreSQL 18 finally carries planner statistics across the upgrade, removing, in
the commit's words, the need for a costly re-analyze after upgrade
in most
cases. Extended statistics still do not transfer.
Carry forwardPre-18, the ANALYZE is part of the outage. From 18, verify the remainder with vacuumdb --missing-stats-only instead of re-analyzing everything.
github.com/postgres/postgres/commit/1fd1bd871
Source
PostgreSQL2023-10
Commit 29d0a77fa: Migrate logical slots to the new node during an upgrade
From PG17, pg_upgrade carries logical replication slots, and deliberately fails on
slots with unconsumed WAL: These checks are needed to prevent data loss.
Before this, every CDC consumer lost its position at every major upgrade.
Carry forwardUpgrading from 17 or later, pause consumers and let the tool carry slots; from anything older, plan the drop-and-resync explicitly.
github.com/postgres/postgres/commit/29d0a77fa
Source
PostgreSQL2022-04
Commit 2c7ea57e5: Revert "Logical decoding of sequences"
The engine's attempt to replicate sequences was reverted whole: issues, mostly
due to combining transactional and non-transactional behavior of sequences. It's not
clear how this could be fixed.
The gap every cutover script works around is a
hard design problem, not neglect.
Carry forwardTreat sequence sync as mandatory cutover choreography until a release ships it; check release notes before deleting your script.
github.com/postgres/postgres/commit/2c7ea57e5
Source
PostgreSQL2021-05
Commit ec4831470: Revert per-index collation version tracking
The feature that would let the database name which indexes a glibc change broke was
committed in 2020 and reverted in 2021 over design problems with composite and
record types. It has not returned.
Carry forwardYou cannot ask the database which text indexes are safe after a locale change. Reindex them all, then REFRESH COLLATION VERSION.
github.com/postgres/postgres/commit/ec4831470
Source
Zalando2026
Spilo: major_upgrade/inplace_upgrade.py
About 700 lines to automate pg_upgrade under Patroni: sanity checks, maintenance
pause, clean shutdown, checkpoint-LSN wait, pg_upgrade -k -j N, rsync to replicas,
DCS identity swap, staged ANALYZE with a statistics-target trick (dropped for PG18+),
then a fresh backup. The script logs its own downtime.
Carry forwardThis file is the honest size of "just run pg_upgrade" in an HA cluster. Read it before estimating your own automation.
github.com/zalando/spilo/.../inplace_upgrade.py
Source
GitLab2026
Runbooks: cutover sequence sync, upgrade procedures, failure signatures
The operational layer in public: explicit sequence-sync steps in both directions in
the decomposition runbook, an OS-upgrade reference architecture built from cascade
clusters, and a log-analysis entry naming duplicate-key errors from a reset sequence
as a known signature.
Carry forwardThe duplicate-key storm right after cutover has one boring cause. Put sequence sync in the runbook and the signature in the triage guide.
gitlab.com/gitlab-com/runbooks/.../zero-downtime-database-decomposition.md
Source
GitLab2026
omnibus-gitlab: gitlab-ctl pg-upgrade
The upgrade shipped to every self-hosted install: a 13-step wrapper that builds a
locale-matched new cluster, copies with pg_upgrade, runs ANALYZE, and if any
errors are detected during this process, it reverts to the old version
. The docs
name degraded performance from missing statistics as the expected failure mode.
Carry forwardCopy mode plus automatic revert is the right default for unattended upgrades; link mode is for watched ones.
gitlab.com/gitlab-org/omnibus-gitlab/.../database.md
Source
pgautoupgrade2026
docker-pgautoupgrade README
A third-party image whose whole purpose in life
is running pg_upgrade --link
on container start, existing because the official Docker PostgreSQL image has no
ability to handle version upgrades
, with the open issue linked. Its first
warning: back up, this is in-place.
Carry forwardIf your platform ships databases in containers, the upgrade path is your product's problem; the base image will not solve it.
github.com/pgautoupgrade/docker-pgautoupgrade
Decision record
Zalando / Patroni2026
Patroni documentation: Major Upgrade of PostgreSQL Version
The HA layer's recorded position: The only possible way to do a major upgrade
currently is: Stop Patroni
, run pg_upgrade by hand, then wipe the DCS state
because the upgraded cluster carries a new system identifier, and re-replicate every
standby.
Carry forwardYour HA system will not own this change. Someone has to write the layer above it, which is what Spilo's script is.
github.com/zalando/patroni/blob/master/docs/existing_data.rst
Decision record
CockroachDB2017-08
RFC: Version Migration
The counterfactual design: cluster version as a setting, rolling-restart upgrades,
and an operator-issued bump that is a promise that the old version is no longer
running on any of the nodes
. Written by an engine that watched this problem from
outside.
Carry forwardWhen evaluating a new engine, ask how it upgrades itself. The answer predicts a decade of your weekends.
github.com/cockroachdb/cockroach/.../20170815_version_migration.md
Vendor
PostgreSQL2024
pg_upgrade and logical replication manuals, REL_17_STABLE
The authoritative statements of the gaps: statistics are not transferred
(through 17), the old cluster is only restartable if --link was not used, DDL and
sequence data are not replicated logically, and --analyze-in-stages exists to make
the statistics gap survivable.
Carry forwardThe manual's Restrictions sections are the checklist of what your cutover script must do. Read them per target version; they shrink each release.
github.com/postgres/postgres/blob/REL_17_STABLE/doc/src/sgml/ref/pgupgrade.sgml
Vendor
Supabase2026
Platform documentation: Upgrading
The managed path, stated plainly: the project goes offline, data is copied and
upgraded with pg_upgrade, downtime scales with size at the disk's throughput, and the
prerequisites are the dropped-state list again: slots dropped, read replicas deleted,
incompatible types removed.
Carry forwardA managed platform does not delete this problem; it fixes the method and hands you the downtime. Read the prerequisites as your compatibility debt.
github.com/supabase/supabase/.../upgrading.mdx
Vendor
Docker / GitLab docs2026
Official image PGDATA restructuring; GitLab OS-upgrade documentation
The official postgres image recently moved PGDATA so that cross-major
pg_upgrade
with --link
works across volume mounts, a decade after the
upgrade issue was opened. GitLab's documentation carries the collation warning:
locale changes in glibc can cause PostgreSQL database files to become
incompatible... leading to index corruption
, with 2.28 named.
Carry forwardThe container image's directory layout and the host's glibc version are part of your database's on-disk format. Pin both; change them deliberately.
gitlab.com/gitlab-org/gitlab/.../upgrading_os.md