PostgreSQLIII · Configuration ArchitectureConfiguration
The precedence ladder beyond the files
What you'll learn
- Order the five levels a setting can be attached to, from least to most specific
- Read pg_db_role_setting and interpret what each row overrides
- Use the source column of pg_settings to identify which level supplied a value
- Apply per-role settings to bound a workload without changing the server default
Prerequisites
Practice
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
The configuration files decide what the server starts with. They do not decide what a given session actually runs with, because four more levels sit above them, each more specific than the last, and each able to override.
This is why “we set work_mem to 4 MB in postgresql.conf” is not an
answer to “how much memory can a query allocate”. It is a statement
about the default.
The five levels
flowchart TD
A["Server configuration\npostgresql.conf + auto.conf"] --> B["Per-database\nALTER DATABASE ... SET"]
B --> C["Per-role\nALTER ROLE ... SET"]
C --> D["Per-role-in-database\nALTER ROLE ... IN DATABASE ... SET"]
D --> E["Session\nSET, or the connection string"]
E --> F["The value the query uses"]
More specific wins. A per-role setting beats a per-database setting;
a session SET beats everything.
Watching it resolve
Two settings were attached at different levels on a live cluster: a
work_mem on the shop database, and a statement_timeout on the
reporting_ro role.
ALTER DATABASE shop SET work_mem = '32MB';
ALTER ROLE reporting_ro SET statement_timeout = '5min';
Both are stored in one catalogue:
$ psql -U postgres -c 'SELECT coalesce(d.datname, ...) AS database, coalesce(r.rolname, ...) AS role, s.setconfig FROM pg_db_role_setting s ...' database | role | setconfig
-----------------+--------------+--------------------------
shop | (all roles) | {work_mem=32MB}
(all databases) | reporting_ro | {statement_timeout=5min}
(2 rows)The full query is worth keeping, because the raw catalogue stores OIDs and zero rather than names and nulls:
psql -U postgres -c \
"SELECT coalesce(d.datname, '(all databases)') AS database,
coalesce(r.rolname, '(all roles)') AS role,
s.setconfig
FROM pg_db_role_setting s
LEFT JOIN pg_database d ON d.oid = s.setdatabase
LEFT JOIN pg_roles r ON r.oid = s.setrole
ORDER BY 1, 2"
Now connect as reporting_ro to shop and ask what is in force:
$ psql -U reporting_ro -d shop -c "SELECT name, setting, source FROM pg_settings WHERE name IN ('work_mem','statement_timeout')" name | setting | source
-------------------+---------+----------
statement_timeout | 300000 | user
work_mem | 32768 | database
(2 rows)source = database means the per-database setting supplied it.
source = user means the per-role setting did — the value name is
user for historical reasons and it means role, which is a genuine
trap when reading this column quickly.
A session SET then beats both:
$ psql -U reporting_ro -d shop -c "SET work_mem='99MB'; SELECT name, setting, source FROM pg_settings WHERE name='work_mem'"SET
name | setting | source
----------+---------+---------
work_mem | 101376 | session
(1 row)What this is genuinely good for
Per-role settings are the mechanism for bounding a workload without touching anybody else, and they are underused.
-- A reporting role that cannot hold a transaction open all night
ALTER ROLE reporting_ro SET statement_timeout = '5min';
ALTER ROLE reporting_ro SET idle_in_transaction_session_timeout = '60s';
-- ...and may use more working memory, because its queries sort large sets
ALTER ROLE reporting_ro SET work_mem = '64MB';
-- A migration role that may take longer to acquire a lock, but not forever
ALTER ROLE migrator SET lock_timeout = '5s';
ALTER ROLE migrator SET statement_timeout = '30min';
Each of those is a policy attached to an identity, applied at
connection time, requiring no application change and no server restart.
The statement_timeout and idle_in_transaction_session_timeout pair
is the single most valuable application of this feature, and Part IV
and Part VII both return to why.
Per-database settings suit properties of the data rather than the
consumer — a database whose workload is analytical wanting a different
default_statistics_target, for instance.
Removing a setting
Each level has its own reset, and using the wrong one silently does nothing:
ALTER DATABASE shop RESET work_mem;
ALTER ROLE reporting_ro RESET statement_timeout;
ALTER ROLE reporting_ro IN DATABASE shop RESET work_mem;
ALTER ROLE reporting_ro RESET ALL; -- every setting for that role
RESET work_mem; -- this session only
The failure worth knowing: running ALTER DATABASE shop RESET work_mem
when the setting was attached to a role removes nothing and reports
success. pg_db_role_setting is where you check which level actually
holds it, and it is the first thing to read rather than the last.
Production discipline
- Read
sourcebefore concluding anything about a value. It names the level in one word and turns a puzzling number into a located one. - Remember
source = usermeans the role level, not the session. The session level reports assession. - Check
pg_db_role_settingbefore editing a file. A per-role or per-database override makes a file change invisible for the sessions that matter. - Use per-role
statement_timeoutandidle_in_transaction_session_timeoutto bound workloads. It needs no application change and no restart, and it is the cheapest control in this course. - Never treat a server-wide
work_memas a ceiling. It isusercontext, any session can raise it, and it is allocated per operation rather than per session. - Reset at the level that set it. Resetting the wrong level reports success and changes nothing.
Cross-course references
- Secrets, PKI & Certificate Management — Part XII (Secret management platforms) covers least-privilege policy design, and the per-role settings here are the database expression of the same idea.
- Observability for Production Sysadmins — Part LIX (Database observability) covers exporting per-session settings so that the gap between the configured default and the effective value is visible.
- Git, CI/CD & GitOps — Part LXXV (Drift) covers detecting configuration that lives outside the repository, which per-role settings in a catalogue very much are.
Quiz
Knowledge check · 6 questions
Q1. pg_settings reports work_mem with source = 'user'. Which level supplied that value?
Q2. An operator lowers work_mem in postgresql.conf during an incident and reloads. Some sessions continue using a much larger value. What is the most likely explanation?
Q3. Which of these are true about per-role and per-database settings? Select all that apply.
Q4. Setting work_mem in postgresql.conf establishes an upper bound on how much working memory any session may allocate.
Q5. List the five levels a PostgreSQL setting can be attached to, from least specific to most specific.
Q6. Explain the discrepancy and give the investigation order.
A capacity review calculated worst-case working memory as max_connections of 200 multiplied by the configured work_mem of 8MB, giving 1.6GB, and concluded the 32GB host had ample headroom. Three weeks later the host was subject to an OOM kill during a nightly batch window. Investigation found that the batch job's sessions were each consuming several hundred megabytes. The configuration file still reads work_mem = 8MB and nobody has edited it.
Passing score: 75%. Answers are checked in this browser.