PostgreSQLII · Installation, Packaging and Service ManagementInstallation
What initdb commits you to, permanently
What you'll learn
- List the initdb decisions that cannot be reversed without rebuilding the cluster
- Explain what changed about data checksums in PostgreSQL 18 and why it matters
- Describe the operational risk a collation change introduces to indexes
- Verify an existing cluster's permanent settings from pg_controldata and pg_database
Prerequisites
Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27
initdb runs once, takes a few seconds, and makes decisions that
outlive every other choice about the cluster. Some of them can be
revisited with a restart. Some require taking the cluster offline. And
some cannot be changed at all without creating a new cluster and moving
the data into it — which, for a production database, means a migration
with downtime rather than a configuration change.
Nothing warns you at the time. initdb succeeds, the cluster works,
and the consequence arrives months later.
The three classes of decision
| Decision | Changeable? | How |
|---|---|---|
shared_buffers, max_connections, most parameters | Yes | Edit and restart |
| Data checksums | Yes, offline | pg_checksums --enable with the cluster stopped |
| WAL segment size | No | New cluster |
| Encoding | No, per database | New database, or new cluster for the default |
| Locale and collation provider | No, per database | New database, or new cluster for the default |
The bottom three are the subject of this lesson.
Data checksums: the PostgreSQL 18 change
Data checksums attach a checksum to every data page, verified on read. They turn silent corruption into a loud error, which is the difference between discovering storage corruption when it happens and discovering it when a customer reports wrong data.
PostgreSQL 18 enables them by default. Every earlier release did not. This is one of the most consequential default changes in the release and it is easy to miss.
The proof is in the tool’s own help text. PostgreSQL 18’s initdb
carries a flag to disable checksums, which exists only because the
default flipped:
$ docker run --rm postgres:18 initdb --help | grep data-checksums-- PostgreSQL 18
-k, --data-checksums use data page checksums
--no-data-checksums do not use data page checksums
-- PostgreSQL 16, for comparison
-k, --data-checksums use data page checksumsConfirm the state of any cluster in two ways:
$ psql -U postgres -c 'SHOW data_checksums' data_checksums
----------------
on
(1 row)
$ pg_controldata "$PGDATA" | grep -i checksum
Data page checksum version: 1Unlike encoding and locale, checksums can be changed after the fact, with the cluster shut down cleanly:
# The cluster must be stopped. pg_checksums refuses to run otherwise.
pg_checksums --enable --progress -D "$PGDATA"
This rewrites every page in the cluster to add its checksum, so it takes time proportional to the data size and generates a great deal of I/O. It is a maintenance-window operation on a large database, not a quick fix, and Part XVII treats it as one.
Encoding and locale: the decisions with no undo
$ psql -U postgres -c 'SELECT datname, pg_encoding_to_char(encoding) AS encoding, datcollate, datctype, datlocprovider FROM pg_database ORDER BY datname' datname | encoding | datcollate | datctype | datlocprovider
-----------+----------+------------+------------+----------------
postgres | UTF8 | en_US.utf8 | en_US.utf8 | c
shop | UTF8 | en_US.utf8 | en_US.utf8 | c
template0 | UTF8 | en_US.utf8 | en_US.utf8 | c
template1 | UTF8 | en_US.utf8 | en_US.utf8 | c
(4 rows)Encoding decides which byte sequences are valid text. UTF8 is the
right answer for essentially every new deployment, and a cluster
created with a legacy single-byte encoding cannot represent characters
outside it. Changing it means dumping the data, creating a new database
with the correct encoding, and loading it back — with whatever
transformation the change requires.
Collation — datcollate — decides sort order. It is the one that
causes production incidents, because it does not merely affect
ORDER BY output. B-tree indexes on text columns are stored in
collation order. An index is a sorted structure, and the sort it was
built with is the collation in force at the time.
datlocprovider names which library implements the collation:
c for the C library, i for ICU, b for PostgreSQL’s own builtin
provider. The value above is c, meaning the operating system’s
glibc decides sort order for these databases.
Choosing at initdb time
# An explicit, deliberate initialisation rather than accepting defaults.
initdb \
--pgdata="$PGDATA" \
--encoding=UTF8 \
--locale-provider=icu \
--icu-locale=en-US \
--data-checksums \
--auth-local=peer \
--auth-host=scram-sha-256
Each of those is a decision worth stating explicitly even when it matches the default, because an explicit value in a provisioning script is a decision somebody made and can be reviewed, while an omitted flag is a default that may change between versions — as checksums just did.
--auth-local and --auth-host seed the initial pg_hba.conf. They
are not permanent, since the file can be edited afterwards, but
starting from scram-sha-256 rather than trust avoids a window in
which the cluster accepts unauthenticated connections. Part V covers
the file properly.
--wal-segsize sets the WAL segment size, defaulting to 16 MB, and it
is genuinely permanent. Very high-write clusters sometimes use a larger
segment to reduce file churn. It is not a decision to make speculatively,
and Part XII gives the reasoning.
Auditing an existing cluster
# The permanent per-database settings
psql -U postgres -c \
"SELECT datname, pg_encoding_to_char(encoding) AS encoding,
datcollate, datctype, datlocprovider
FROM pg_database ORDER BY datname"
# The permanent cluster-wide settings
pg_controldata "$PGDATA" | grep -iE 'checksum|segment size|catalog version|state'
# Which collations exist, and which provider each uses
psql -U postgres -c \
"SELECT collname, collprovider, collversion
FROM pg_collation WHERE collname NOT LIKE 'pg_%' LIMIT 10"
pg_collation.collversion is worth knowing about. PostgreSQL records
the collation library version an index was built against where the
provider supplies one, and warns when it detects a mismatch. It is a
partial defence rather than a complete one — it does not cover every
provider and every case — but a warning about a collation version
mismatch in the log is never noise, and Part XVIII treats it as a
corruption-class signal.
Production discipline
- State every
initdboption explicitly in provisioning code, even where it matches the current default. Defaults change between versions, as checksums did in 18. - Decide the collation provider deliberately.
libcties sort order to the operating system’s C library version; ICU and the builtin provider do not. - Reindex text indexes after any operating system upgrade beneath a libc-collated cluster, and after moving a data directory between hosts. The failure is silent and returns wrong results rather than errors.
- Check checksum agreement before a major upgrade.
pg_upgraderequires the source and target to match, and the 18 default makes a mismatch likely for anything upgraded from 17 or earlier. - Read
pg_controldataon any cluster you inherit. It reports the permanent decisions, and it works on a stopped cluster.
Cross-course references
- Linux for Production Sysadmins — Part XI (Packages) and Part LXV (Kernel upgrades) cover the operating system upgrades that change glibc beneath a database, which is the trigger for the collation hazard above.
- Ansible for Production Sysadmins — Part XII (Idempotency) covers
why a provisioning role should state these values rather than rely on
defaults, since
initdbis not idempotent and runs once. - Ceph & Distributed Storage — Part CXVIII (Data integrity) covers the storage-layer checksums that complement the page checksums discussed here.
Quiz
Knowledge check · 6 questions
Q1. A pg_upgrade from PostgreSQL 17 to 18 refuses to run, reporting a mismatch between the old and new clusters. The 17 cluster was created in 2024 and the 18 cluster was initialised yesterday with default options. What is the most likely cause?
Q2. Which of these is genuinely impossible to change on an existing cluster without creating a new one and migrating the data into it?
Q3. A cluster upgraded from PostgreSQL 17 to 18 automatically gains data checksums, because 18 enables them by default.
Q4. When the collation provider is libc, a glibc upgrade on the host can leave existing text indexes sorted according to rules the database no longer uses.
Q5. You have restored a physical backup of a PostgreSQL cluster onto a host running a newer operating system release. Name the specific risk this creates and the action that addresses it.
Q6. Explain the mechanism and give the investigation order.
A customer-facing search feature began returning incomplete results three weeks ago. Rows that certainly exist are not returned by queries filtering on a text column, but the same rows appear when the query is rewritten to force a sequential scan. No application deployment took place in that window. The database is PostgreSQL 17 with datlocprovider of c. The platform team confirms the underlying virtual machines were rebuilt onto a newer base image on the same weekend the reports began, as part of routine patching, and the data directory was moved across on a detached volume.
Passing score: 75%. Answers are checked in this browser.