Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

PostgreSQL in Depth  Module 1 – How PostgreSQL works and how to run it

Creating a cluster: initdb, settings, ALTER SYSTEM

Create a PostgreSQL 18 cluster with initdb, start and reload it with pg_ctl, and change settings at server, database, role and session level.

  • Beginner
  • 10 minutes
  • Examples run with PostgreSQL 18.6 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 10 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Create a PostgreSQL 18 cluster with initdb, choosing the encoding, locale provider and checksums deliberately
  • Start, stop and reload a server with pg_ctl, and tell which settings need a restart
  • Change a setting for the server, a database, a role, a session or one transaction
  • Trace where a setting's current value came from with pg_settings and pg_file_settings

Before you start

What you’ll learn · Before you start

What you will learn

  • Create a PostgreSQL 18 cluster with initdb, choosing the encoding, locale provider and checksums deliberately
  • Start, stop and reload a server with pg_ctl, and tell which settings need a restart
  • Change a setting for the server, a database, a role, a session or one transaction
  • Trace where a setting's current value came from with pg_settings and pg_file_settings

Before you start

On this page

A report needs work_mem at 256MB and a migration needs lock_timeout at 2 seconds: put either value in postgresql.conf and every one of the server’s 100 connections gets it. initdb creates a cluster with its defaults, ALTER SYSTEM changes a value for the whole server, and narrower levels change it for one database, one role, one session or one transaction; choosing the right level is most of configuring PostgreSQL well.

You will create a cluster with initdb and make its choices on purpose, start, reload and restart it with pg_ctl, set values with ALTER SYSTEM, per database, per role and per transaction, and trace where any value on a running server came from.

initdb makes choices that are hard to change later

A database cluster: One PostgreSQL data directory and the server that runs it, holding any number of databases that share its settings, roles and write-ahead log. initdb creates it. is one data directory and the one server that runs it, holding any number of databases. initdb creates it: the folders, the catalogs shared by the whole cluster, and three databases: postgres for everyday use, and template1 and template0, the templates a later CREATE DATABASE copies (template1 unless it names another). Here it runs twice, once with the defaults and once with the builtin locale provider:

initdb with the defaults, then with the builtin locale provider

initdb_session.py

"""Creating a PostgreSQL 18.6 cluster with initdb, starting it with pg_ctl, and a second cluster that takes its
collation from PostgreSQL's builtin locale provider instead of the operating system."""

from pg_session import Server

with Server() as pg:
    pg.initdb(show=True)
    pg.start(show=True)
    pg.psql("checks.sql")

with Server(initdb=["--locale-provider=builtin", "--builtin-locale=C.UTF-8"]) as builtin:
    builtin.initdb(show=True)

Output

$ initdb -D data -U postgres --auth=scram-sha-256 --pwfile=pwfile
The files belonging to this database system will be owned by user "learner".
This user must also own the server process.

The database cluster will be initialized with locale "en_US.UTF-8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".

Data page checksums are enabled.

creating directory data ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default "max_connections" ... 100
selecting default "shared_buffers" ... 128MB
selecting default time zone ... UTC
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

Success. You can now start the database server using:

    pg_ctl -D data -l logfile start

$ pg_ctl -D data -l data.log -w -t 60 start
waiting for server to start... done
server started

$ psql -X -a -f checks.sql
-- What did initdb choose? Checksums, and the locale of the databases it created.
SHOW data_checksums;
 data_checksums 
----------------
 on
(1 row)

SELECT datname, datlocprovider, datcollate, pg_encoding_to_char(encoding) AS encoding
FROM pg_database ORDER BY datname;
  datname  | datlocprovider | datcollate  | encoding 
-----------+----------------+-------------+----------
 postgres  | c              | en_US.UTF-8 | UTF8
 template0 | c              | en_US.UTF-8 | UTF8
 template1 | c              | en_US.UTF-8 | UTF8
(3 rows)

$ initdb -D data -U postgres --auth=scram-sha-256 --pwfile=pwfile --locale-provider=builtin --builtin-locale=C.UTF-8
The files belonging to this database system will be owned by user "learner".
This user must also own the server process.

The database cluster will be initialized with this locale configuration:
  locale provider:   builtin
  default collation: C.UTF-8
  LC_COLLATE:  en_US.UTF-8
  LC_CTYPE:    en_US.UTF-8
  LC_MESSAGES: en_US.UTF-8
  LC_MONETARY: en_US.UTF-8
  LC_NUMERIC:  en_US.UTF-8
  LC_TIME:     en_US.UTF-8
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".

Data page checksums are enabled.

creating directory data ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default "max_connections" ... 100
selecting default "shared_buffers" ... 128MB
selecting default time zone ... UTC
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

Success. You can now start the database server using:

    pg_ctl -D data -l logfile start

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 initdb_session.py

checks.sql

-- What did initdb choose? Checksums, and the locale of the databases it created.
SHOW data_checksums;
SELECT datname, datlocprovider, datcollate, pg_encoding_to_char(encoding) AS encoding
FROM pg_database ORDER BY datname;

pg_session.py

"""Throwaway PostgreSQL 18.6 servers for the examples of this lesson.

Every session creates a cluster of its own with initdb in a new folder inside the examples folder, starts it, runs
psql scripts and server programs on it, prints what they printed, and stops and deletes it again, also when something
fails. The server is the pinned PostgreSQL 18.6 from the mise installations next to the Python that runs this file.
It listens only on 127.0.0.1, an address no other computer can reach, on a port the operating system reports as
unused, with no Unix socket, and it asks for a password (scram-sha-256) made up for the run and never printed.
learn:verify's run sandbox cannot read the home folder, where mise keeps PostgreSQL, so these sessions are recorded
with LEARN_VERIFY_NETWORK=allow; they read only this folder and the toolchain and connect to nothing but 127.0.0.1.

The output is exactly what the programs printed, with four substitutions so that a record never shows the computer
it ran on and is the same on every run: the run's folder is shown as ".", the folder of PostgreSQL's programs is left
out of their paths, the operating-system user's name is shown as "learner", and the dots pg_ctl prints while it waits
(one about every tenth of a second) as three. The sessions never print the port. A session stops with status 1 when
a command fails that should have worked, or reports a different number of errors than the session expects, so a run
that went wrong is never recorded as if it had worked.
"""

import os
import pwd
import re
import secrets
import shlex
import shutil
import socket
import subprocess
import sys
import tempfile
import textwrap
import time
from pathlib import Path

PG_VERSION = "18.6"
# .../installs/python/3.14.8/bin/python3 -> .../installs
INSTALLS = Path(sys.executable).resolve().parents[3]
BIN = INSTALLS / "postgres" / PG_VERSION / "bin"
OS_USER = pwd.getpwuid(os.geteuid()).pw_name


def free_port():
    """A TCP port on 127.0.0.1 that nothing listens on now."""
    with socket.socket() as probe:
        probe.bind(("127.0.0.1", 0))
        return probe.getsockname()[1]


class Server:
    """One throwaway cluster: initdb, settings, start; stopped and deleted when the with block ends."""

    def __init__(self, settings=None, initdb=(), name="data"):
        self.settings = dict(settings or {})
        self.initdb_args = list(initdb)
        self.name = name
        self.running = []

    def __enter__(self):
        if not (BIN / "postgres").exists():
            sys.exit(f"PostgreSQL {PG_VERSION} was not found next to this Python")
        self.base = Path(tempfile.mkdtemp(prefix="pg", dir=".")).resolve()
        self.port = free_port()
        self.password = secrets.token_urlsafe(24)
        (self.base / "pwfile").write_text(self.password + "\n")
        self.env = {**os.environ, "PGHOST": "127.0.0.1", "PGPORT": str(self.port), "PGUSER": "postgres",
                    "PGPASSWORD": self.password, "PGDATABASE": "postgres", "PGCONNECT_TIMEOUT": "10"}
        for key in ("PGSERVICE", "PGSERVICEFILE", "PGOPTIONS", "PSQLRC"):
            self.env.pop(key, None)
        for script in Path(".").glob("*.sql"):
            shutil.copy(script, self.base / script.name)
        for data in Path(".").glob("*.csv"):
            shutil.copy(data, self.base / data.name)
        return self

    def __exit__(self, *exc):
        for folder, _port in reversed(self.running):
            self._call(["pg_ctl", "-D", folder, "-m", "fast", "-w", "-t", "60", "stop"])
        self.running = []
        shutil.rmtree(self.base, ignore_errors=True)

    # ---- the cluster ---------------------------------------------------------------------------------------------
    def initdb(self, show=False):
        args = ["initdb", "-D", self.name, "-U", "postgres", "--auth=scram-sha-256", "--pwfile=pwfile",
                *self.initdb_args]
        if not show:
            args.append("--no-instructions")
        self._expect(args, show=show)
        with open(self.base / self.name / "postgresql.conf", "a") as conf:
            conf.write("\n# Added by the session: 127.0.0.1 only, no Unix socket.\n")
            conf.write(f"listen_addresses = '127.0.0.1'\nport = {self.port}\nunix_socket_directories = ''\n")
            for key, value in self.settings.items():
                conf.write(f"{key} = {value}\n")

    def start(self, show=False, folder=None, port=None):
        folder = folder or self.name
        self._expect(["pg_ctl", "-D", folder, "-l", f"{folder}.log", "-w", "-t", "60", "start"], show=show)
        self.running.append((folder, port or self.port))

    def up(self):
        """initdb and start, quietly."""
        self.initdb()
        self.start()
        return self

    def standby(self, folder="standby"):
        """A streaming standby of this server, made with pg_basebackup -R, started on a port of its own."""
        self._expect(["pg_basebackup", "-D", folder, "-R", "-X", "stream", "-c", "fast"])
        port = free_port()
        with open(self.base / folder / "postgresql.conf", "a") as conf:
            conf.write(f"port = {port}\n")
        self.start(folder=folder, port=port)
        for _ in range(300):
            if self.query("SELECT count(*) FROM pg_stat_replication WHERE state = 'streaming'") == "1":
                return port
            time.sleep(0.1)
        sys.exit("the standby did not start streaming")

    # ---- running things ------------------------------------------------------------------------------------------
    def _call(self, args, env=None):
        args = [str(a) for a in args]
        done = subprocess.run([str(BIN / args[0]), *args[1:]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                              text=True, cwd=self.base, env=env or self.env)
        return done.returncode, self.clean(done.stdout)

    def _expect(self, args, show=False, status=0):
        code, out = self._call(args)
        if show:
            _show(args, out)
        if code != status:
            if not show:
                print(out)
            sys.exit(f"{args[0]} ended with status {code}, not {status}")
        return out

    def clean(self, text):
        """The run's folder as ".", the user as learner, pg_ctl's waiting dots as three (see the docstring)."""
        text = text.replace(str(BIN) + "/", "").replace(str(self.base) + "/", "").replace(str(self.base), ".")
        text = re.sub(r"(waiting for server to (?:start|shut down))\.+", r"\1...", text)
        return text.replace(f'"{OS_USER}"', '"learner"')

    def wait_for(self, sql, value):
        """Waits (unprinted) until a query returns the value, for example after a reload."""
        for _ in range(300):
            if self.query(sql) == value:
                return
            time.sleep(0.1)
        sys.exit(f"{sql} never returned {value}")

    def show_file(self, path):
        """Prints a text file of the cluster, as `cat` would."""
        print(f"$ cat {path}")
        print((self.base / path).read_text().rstrip("\n"))
        print()

    def query(self, sql, port=None):
        """One value from one query, unprinted (the session uses it to wait or to decide)."""
        env = {**self.env, "PGPORT": str(port or self.port)}
        code, out = self._call(["psql", "-X", "-At", "-c", sql], env=env)
        if code != 0:
            sys.exit(f"the query failed: {out}")
        return out.strip()

    def silent(self, sql):
        """Runs a statement without printing it (used only to give a role the run's password)."""
        self.query(sql)

    def psql(self, script, errors=0, status=None, user=None, db=None, port=None, flags=("-a",), note=None):
        """Runs a psql script with -X (no ~/.psqlrc) and the given flags, and prints the command and its output.
        `errors` is how many ERROR lines the script must print; `status` the exit status it must end with."""
        env = {**self.env, "PGPORT": str(port or self.port)}
        if user:
            env["PGUSER"] = user
        if db:
            env["PGDATABASE"] = db
        args = ["psql", "-X", *flags, "-f", script]
        code, out = self._call(args, env=env)
        shown = ["psql", "-X", *flags, "-f", script]
        if user:
            shown[1:1] = ["-U", user]
        if db:
            shown[1:1] = ["-d", db]
        if note:
            print(f"-- {note}")
        _show(shown, out)
        found = sum(1 for line in out.splitlines() if "ERROR:" in line or ": error:" in line)
        expected_status = 0 if status is None else status
        if found != errors or code != expected_status:
            sys.exit(f"{script}: {found} errors and status {code}; the session expects {errors} and {expected_status}")

    def sessions(self, count):
        """Opens `count` idle psql connections (each one a client backend) until the with block ends."""
        return _Idle(self, count)

    def shell(self, *args, status=0):
        """Runs one of PostgreSQL's programs and prints the command and its output."""
        return self._expect(list(args), show=True, status=status)

    def listing(self, folder, prefix=""):
        """Prints the names in a folder of the cluster (sorted, folders with a slash), as `ls -p` would list them."""
        path = self.base / folder
        names = sorted(n + ("/" if (path / n).is_dir() else "") for n in os.listdir(path) if n.startswith(prefix))
        print(f"-- in {folder}" + (f", the names that start with {prefix}" if prefix else "") + ":")
        print(textwrap.fill("  ".join(names), width=78, initial_indent="   ", subsequent_indent="   ",
                            break_on_hyphens=False))
        print()
        return names


def _arg(a):
    """A command-line argument as a reader would type it: double quotes around text with single quotes in it."""
    a = str(a)
    if re.fullmatch(r"[\w@%+=:,./-]+", a):
        return a
    if not re.search(r"[\"$`\\!]", a):
        return f'"{a}"'
    return shlex.quote(a)


def _show(args, out):
    print("$ " + " ".join(_arg(a) for a in args))
    if out.strip():
        print(out.rstrip("\n"))
    print()


class _Idle:
    """Idle psql connections, closed again when the with block ends."""

    def __init__(self, server, count):
        self.server, self.count, self.procs = server, count, []

    def __enter__(self):
        before = int(self.server.query("SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend'"))
        for _ in range(self.count):
            self.procs.append(subprocess.Popen([str(BIN / "psql"), "-X", "-q"], stdin=subprocess.PIPE,
                                               stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                                               env=self.server.env, cwd=self.server.base))
        sql = "SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend'"
        for _ in range(300):
            # the query's own connection is one more client backend
            if int(self.server.query(sql)) == before + self.count:
                return self
            time.sleep(0.1)
        sys.exit("the idle connections did not open")

    def __exit__(self, *exc):
        for p in self.procs:
            p.stdin.close()
            p.wait(timeout=30)

Read the first output from the top. The superuser is named postgres because of -U postgres; without it initdb uses the name of the operating-system user. Its password came from a file, --pwfile, so it never appears on a command line, and --auth=scram-sha-256 makes pg_hba.conf ask every client for it. The manual warns that trust, the default when no method is given, should be used only if you trust every local user of the machine.

Data page checksums are enabled. That line is new in PostgreSQL 18, which turned checksums on by default; a checksum on every page lets the server detect corruption from the storage, and --no-data-checksums turns them off. pg_upgrade needs the same choice on the old and the new cluster, which is why the option exists.

The locale came from the environment, en_US.UTF-8, and so did the encoding, UTF8. With the default provider, libc, sorting and case rules come from the operating system’s C library, so they follow whatever that library does on each machine. The second run chose --locale-provider=builtin --builtin-locale=C.UTF-8: PostgreSQL’s own Unicode rules, which compare code points and do not depend on the operating system.

Some choices can never change afterwards. --wal-segsize, 16 MB by default, is fixed when the cluster is created, and an existing database keeps its encoding and locale: a database with others is created from template0 and the data is moved into it.

Checkpoint

Check: What initdb decides

  1. Question 1 of 2 You run initdb from PostgreSQL 18 without any checksum option. What do you get?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: A cluster with data page checksums enabled, as the recorded output said

    Since PostgreSQL 18 every new cluster gets data page checksums unless --no-data-checksums is given, and SHOW data_checksums reported on. pg_upgrade needs the same setting on the old and new clusters.

  2. Question 2 of 2 A team wants sorting rules that do not change when the database moves to a machine with another operating system. Which initdb choice from the lesson gives them that?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: --locale-provider=builtin with --builtin-locale=C.UTF-8

    The second recorded initdb printed "locale provider: builtin, default collation: C.UTF-8". ICU is the other provider that does not use the operating system's library.

Every setting has a context that says when a change applies

A running server has about 400 settings, and the view pg_settings lists every one with its current value, its unit, where the value came from (source) and its context, which says how a change can take effect. The run reads six settings, counts the contexts and changes work_mem twice:

pg_settings, SET LOCAL and SET

settings_session.py

"""Reading pg_settings, and setting a value for one transaction and for one session."""

from pg_session import Server

with Server() as pg:
    pg.up()
    pg.psql("settings.sql", errors=1)

Output

$ psql -X -a -f settings.sql
-- Where does each value come from, and when can it change? context answers the second question.
SELECT name, setting, unit, context, source FROM pg_settings
WHERE name IN ('shared_buffers', 'max_connections', 'listen_addresses', 'log_min_duration_statement',
               'work_mem', 'lock_timeout')
ORDER BY context, name;
            name            |  setting  | unit |  context   |       source       
----------------------------+-----------+------+------------+--------------------
 listen_addresses           | 127.0.0.1 |      | postmaster | configuration file
 max_connections            | 100       |      | postmaster | configuration file
 shared_buffers             | 16384     | 8kB  | postmaster | configuration file
 log_min_duration_statement | -1        | ms   | superuser  | default
 lock_timeout               | 0         | ms   | user       | default
 work_mem                   | 4096      | kB   | user       | default
(6 rows)

SELECT context, count(*) FROM pg_settings GROUP BY context ORDER BY count(*) DESC, context;
      context      | count 
-------------------+-------
 user              |   151
 sighup            |   104
 postmaster        |    69
 superuser         |    49
 internal          |    20
 superuser-backend |     4
 backend           |     2
(7 rows)

-- SET LOCAL lasts until the transaction ends; SET until the session ends or RESET.
BEGIN;
BEGIN
SET LOCAL work_mem = '64MB';
SET
SHOW work_mem;
 work_mem 
----------
 64MB
(1 row)

COMMIT;
COMMIT
SHOW work_mem;
 work_mem 
----------
 4MB
(1 row)

SET work_mem = '16MB';
SET
SELECT setting, unit, source FROM pg_settings WHERE name = 'work_mem';
 setting | unit | source  
---------+------+---------
 16384   | kB   | session
(1 row)

RESET work_mem;
RESET
SHOW work_mem;
 work_mem 
----------
 4MB
(1 row)

-- A postmaster setting cannot change inside a running server.
SET shared_buffers = '256MB';
psql:settings.sql:18: ERROR:  parameter "shared_buffers" cannot be changed without restarting the server

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 settings_session.py

settings.sql

-- Where does each value come from, and when can it change? context answers the second question.
SELECT name, setting, unit, context, source FROM pg_settings
WHERE name IN ('shared_buffers', 'max_connections', 'listen_addresses', 'log_min_duration_statement',
               'work_mem', 'lock_timeout')
ORDER BY context, name;
SELECT context, count(*) FROM pg_settings GROUP BY context ORDER BY count(*) DESC, context;
-- SET LOCAL lasts until the transaction ends; SET until the session ends or RESET.
BEGIN;
SET LOCAL work_mem = '64MB';
SHOW work_mem;
COMMIT;
SHOW work_mem;
SET work_mem = '16MB';
SELECT setting, unit, source FROM pg_settings WHERE name = 'work_mem';
RESET work_mem;
SHOW work_mem;
-- A postmaster setting cannot change inside a running server.
SET shared_buffers = '256MB';
Context How a change takes effect Examples
postmaster Only when the server restarts (69 settings here) shared_buffers, max_connections, listen_addresses
sighup When the server reloads its configuration files (104) log_line_prefix, autovacuum
superuser In a session, by a superuser or a role granted SET on it (49) log_min_duration_statement
user In any session, by any user (151) work_mem, lock_timeout
internal Never: fixed when PostgreSQL was built or the cluster created (20) block_size, data_checksums

Two rarer contexts, backend and superuser-backend (6 settings here), take their value when a session starts and never change inside it.

SET LOCAL work_mem = '64MB' lasted until the COMMIT, and SHOW work_mem then gave 4MB again. That is the safe way to give one report more memory: inside its transaction and nowhere else. A plain SET lasted until the RESET, and pg_settings reported its source as session. The last statement tried to SET shared_buffers and failed with “cannot be changed without restarting the server”, which is what context postmaster means.

Checkpoint

Check: Setting contexts

  1. Question 1 of 2 pg_settings shows max_connections with context postmaster. How can its value be changed?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Only by restarting the server after changing it in a configuration file or with ALTER SYSTEM

    The context column says how a change takes effect: postmaster at a restart, sighup at a reload, superuser and user also with SET, and internal never.

  2. Question 2 of 2 Inside BEGIN … COMMIT a report runs SET LOCAL work_mem = '64MB', then its query, then COMMIT. What is work_mem in the same session after the COMMIT?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The value the session had before, 4MB in the recorded run

    The recorded SHOW work_mem gave 64MB before the COMMIT and 4MB after it. That makes SET LOCAL the safe way to give one transaction more memory.

ALTER SYSTEM writes a file that a reload or a restart applies

ALTER SYSTEM changes a setting for the whole server from SQL. It writes the value into postgresql.auto.conf: The configuration file in the data directory that ALTER SYSTEM writes. The server reads it after postgresql.conf, so a value set there overrides the same setting in postgresql.conf. in the data directory, which the server reads after postgresql.conf, so its values win. Nothing changes until the configuration is reloaded, or, for a postmaster setting, until a restart. The run changes work_mem with a reload and shared_buffers with a restart:

ALTER SYSTEM, a reload and a restart Python · alter_system_session.py
"""ALTER SYSTEM and postgresql.auto.conf: a reload for work_mem, a restart for shared_buffers."""

from pg_session import Server

with Server() as pg:
    pg.up()
    # psql sends one -c string as one request, so its two statements run in one transaction block.
    pg.shell("psql", "-X", "-c", "ALTER SYSTEM SET work_mem = '8MB'; SELECT pg_reload_conf();", status=1)
    pg.shell("psql", "-X", "-c", "ALTER SYSTEM SET work_mem = '8MB'", "-c", "SELECT pg_reload_conf()")
    pg.wait_for("SHOW work_mem", "8MB")
    pg.show_file("data/postgresql.auto.conf")
    pg.psql("file_settings.sql")
    pg.shell("psql", "-X", "-c", "ALTER SYSTEM SET shared_buffers = '256MB'", "-c", "SELECT pg_reload_conf()")
    pg.wait_for("SELECT pending_restart FROM pg_settings WHERE name = 'shared_buffers'", "t")
    pg.psql("pending.sql")
    pg.shell("pg_ctl", "-D", "data", "-l", "data.log", "restart")
    pg.shell("psql", "-X", "-c", "SHOW shared_buffers")

Output

$ psql -X -c "ALTER SYSTEM SET work_mem = '8MB'; SELECT pg_reload_conf();"
ERROR:  ALTER SYSTEM cannot run inside a transaction block

$ psql -X -c "ALTER SYSTEM SET work_mem = '8MB'" -c "SELECT pg_reload_conf()"
ALTER SYSTEM
 pg_reload_conf 
----------------
 t
(1 row)

$ cat data/postgresql.auto.conf
# Do not edit this file manually!
# It will be overwritten by the ALTER SYSTEM command.
work_mem = '8MB'

$ psql -X -a -f file_settings.sql
-- Which file and line set work_mem, and does every entry in the files apply?
SELECT name, setting, unit, source, pending_restart FROM pg_settings WHERE name = 'work_mem';
   name   | setting | unit |       source       | pending_restart 
----------+---------+------+--------------------+-----------------
 work_mem | 8192    | kB   | configuration file | f
(1 row)

SELECT regexp_replace(sourcefile, '^.*/', '') AS file, sourceline, name, setting, applied, error
FROM pg_file_settings WHERE name IN ('max_connections', 'shared_buffers', 'work_mem') ORDER BY seqno;
         file         | sourceline |      name       | setting | applied | error 
----------------------+------------+-----------------+---------+---------+-------
 postgresql.conf      |         65 | max_connections | 100     | t       | 
 postgresql.conf      |        132 | shared_buffers  | 128MB   | t       | 
 postgresql.auto.conf |          3 | work_mem        | 8MB     | t       | 
(3 rows)

$ psql -X -c "ALTER SYSTEM SET shared_buffers = '256MB'" -c "SELECT pg_reload_conf()"
ALTER SYSTEM
 pg_reload_conf 
----------------
 t
(1 row)

$ psql -X -a -f pending.sql
-- shared_buffers has context postmaster: a reload reads the new value but cannot apply it.
SELECT name, setting, unit, pending_restart FROM pg_settings WHERE name = 'shared_buffers';
      name      | setting | unit | pending_restart 
----------------+---------+------+-----------------
 shared_buffers | 16384   | 8kB  | t
(1 row)

SELECT regexp_replace(sourcefile, '^.*/', '') AS file, name, setting, applied, error
FROM pg_file_settings WHERE name = 'shared_buffers' ORDER BY seqno;
         file         |      name      | setting | applied |            error             
----------------------+----------------+---------+---------+------------------------------
 postgresql.conf      | shared_buffers | 128MB   | f       | 
 postgresql.auto.conf | shared_buffers | 256MB   | f       | setting could not be applied
(2 rows)

$ pg_ctl -D data -l data.log restart
waiting for server to shut down... done
server stopped
waiting for server to start... done
server started

$ psql -X -c "SHOW shared_buffers"
 shared_buffers 
----------------
 256MB
(1 row)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 alter_system_session.py

The first command failed: “ALTER SYSTEM cannot run inside a transaction block”. psql sends a -c string with two statements as one request, which runs as one transaction, and ALTER SYSTEM writes a file that no ROLLBACK could take back, so the manual forbids it in a transaction. Two -c options run as two requests, and the second try worked.

pg_file_settings reads the configuration files again and reports every entry, with the file, the line and whether it can be applied: work_mem = 8MB came from line 3 of postgresql.auto.conf. After the shared_buffers change and a reload, pg_settings still showed 16384 pages but pending_restart was true, and pg_file_settings reported the new entry as “setting could not be applied”. Only pg_ctl restart made SHOW shared_buffers say 256MB.

pg_ctl is the program that starts, stops, restarts and reloads a server:

Command What it does
pg_ctl -D data -l data.log start Starts the postmaster in the background, logging to data.log, and waits until it accepts connections
pg_ctl -D data stop -m fast Fast, the default mode: rolls back open transactions, disconnects clients and shuts down cleanly
pg_ctl -D data stop -m smart Refuses new connections and waits for every client to leave
pg_ctl -D data stop -m immediate Ends every process without a clean shutdown; the next start runs crash recovery
pg_ctl -D data reload Sends the postmaster SIGHUP: the configuration files and pg_hba.conf are read again
pg_ctl -D data restart A stop followed by a start, needed for postmaster settings

SELECT pg_reload_conf() does the same as pg_ctl reload from SQL.

Editing postgresql.conf while ALTER SYSTEM has set the same value

A value in postgresql.auto.conf wins over postgresql.conf, so changing work_mem in postgresql.conf and reloading changes nothing while ALTER SYSTEM has set it. ALTER SYSTEM RESET work_mem removes the entry, and pg_file_settings shows both entries and which one is applied.

Checkpoint

Check: ALTER SYSTEM, reload and restart

  1. Question 1 of 2 psql -c "ALTER SYSTEM SET work_mem = '8MB'; SELECT pg_reload_conf();" fails with "ALTER SYSTEM cannot run inside a transaction block". Why, and what works?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: One -c string is one request, run as one transaction; two -c options, one per statement, work

    ALTER SYSTEM writes a file, and no ROLLBACK could take that write back, so PostgreSQL refuses it inside a transaction block or a function. psql -c with two separate options sends two requests.

  2. Question 2 of 2 After ALTER SYSTEM SET shared_buffers = '256MB' and a reload, SHOW shared_buffers still says 128MB. What do the views say, and what applies the value?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: pg_settings shows pending_restart true and pg_file_settings reports the entry as not applied; a restart applies it

    A reload reads every entry, applies what it can and marks postmaster settings as pending. pending_restart in pg_settings and the error column of pg_file_settings tell you the change is waiting.

Databases and roles carry their own defaults

ALTER DATABASE … SET and ALTER ROLE … SET store a value that a session receives when it starts, kept in the catalog pg_db_role_setting. ALTER ROLE … IN DATABASE … SET stores one for one role in one database. The run gives a reporting role 64MB of work_mem, the shop database a 2-second lock_timeout, and the reporting role 32MB when it works in shop, then logs in as reporting twice:

Defaults for a database, a role, and the role in that database

levels_session.py

"""Defaults stored for a role, for a database and for the pair, and which one a login receives."""

from pg_session import Server

with Server() as pg:
    pg.up()
    pg.psql("levels.sql")
    pg.silent(f"ALTER ROLE reporting PASSWORD '{pg.password}'")
    pg.psql("whoami.sql", user="reporting", db="shop")
    pg.psql("whoami.sql", user="reporting", db="postgres")
    pg.psql("set_role.sql", db="shop")

Output

$ psql -X -a -f levels.sql
-- A role and a database, each with stored defaults, and one default for the pair.
CREATE ROLE reporting LOGIN;
CREATE ROLE
CREATE DATABASE shop;
CREATE DATABASE
ALTER ROLE reporting SET work_mem = '64MB';
ALTER ROLE
ALTER DATABASE shop SET lock_timeout = '2s';
ALTER DATABASE
ALTER ROLE reporting IN DATABASE shop SET work_mem = '32MB';
ALTER ROLE
SELECT coalesce(r.rolname, '(every role)') AS role, coalesce(d.datname, '(every database)') AS database,
       s.setconfig
FROM pg_db_role_setting AS s
LEFT JOIN pg_roles AS r ON r.oid = s.setrole
LEFT JOIN pg_database AS d ON d.oid = s.setdatabase
ORDER BY 1, 2;
     role     |     database     |     setconfig     
--------------+------------------+-------------------
 (every role) | shop             | {lock_timeout=2s}
 reporting    | (every database) | {work_mem=64MB}
 reporting    | shop             | {work_mem=32MB}
(3 rows)

-- The session now gives reporting a password made up for this run, and never prints it.

$ psql -d shop -U reporting -X -a -f whoami.sql
-- Which defaults did this login receive, and from which level?
SELECT current_user, current_database(), current_setting('work_mem') AS work_mem,
       current_setting('lock_timeout') AS lock_timeout;
 current_user | current_database | work_mem | lock_timeout 
--------------+------------------+----------+--------------
 reporting    | shop             | 32MB     | 2s
(1 row)

SELECT name, setting, unit, source FROM pg_settings WHERE name IN ('lock_timeout', 'work_mem') ORDER BY name;
     name     | setting | unit |    source     
--------------+---------+------+---------------
 lock_timeout | 2000    | ms   | database
 work_mem     | 32768   | kB   | database user
(2 rows)

$ psql -d postgres -U reporting -X -a -f whoami.sql
-- Which defaults did this login receive, and from which level?
SELECT current_user, current_database(), current_setting('work_mem') AS work_mem,
       current_setting('lock_timeout') AS lock_timeout;
 current_user | current_database | work_mem | lock_timeout 
--------------+------------------+----------+--------------
 reporting    | postgres         | 64MB     | 0
(1 row)

SELECT name, setting, unit, source FROM pg_settings WHERE name IN ('lock_timeout', 'work_mem') ORDER BY name;
     name     | setting | unit | source  
--------------+---------+------+---------
 lock_timeout | 0       | ms   | default
 work_mem     | 65536   | kB   | user
(2 rows)

$ psql -d shop -X -a -f set_role.sql
-- SET ROLE changes the role's privileges, not its stored defaults: those apply only at login.
SET ROLE reporting;
SET
SELECT current_user, current_database(), current_setting('work_mem') AS work_mem;
 current_user | current_database | work_mem 
--------------+------------------+----------
 reporting    | shop             | 4MB
(1 row)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 levels_session.py

levels.sql

-- A role and a database, each with stored defaults, and one default for the pair.
CREATE ROLE reporting LOGIN;
CREATE DATABASE shop;
ALTER ROLE reporting SET work_mem = '64MB';
ALTER DATABASE shop SET lock_timeout = '2s';
ALTER ROLE reporting IN DATABASE shop SET work_mem = '32MB';
SELECT coalesce(r.rolname, '(every role)') AS role, coalesce(d.datname, '(every database)') AS database,
       s.setconfig
FROM pg_db_role_setting AS s
LEFT JOIN pg_roles AS r ON r.oid = s.setrole
LEFT JOIN pg_database AS d ON d.oid = s.setdatabase
ORDER BY 1, 2;
-- The session now gives reporting a password made up for this run, and never prints it.

whoami.sql

-- Which defaults did this login receive, and from which level?
SELECT current_user, current_database(), current_setting('work_mem') AS work_mem,
       current_setting('lock_timeout') AS lock_timeout;
SELECT name, setting, unit, source FROM pg_settings WHERE name IN ('lock_timeout', 'work_mem') ORDER BY name;

set_role.sql

-- SET ROLE changes the role's privileges, not its stored defaults: those apply only at login.
SET ROLE reporting;
SELECT current_user, current_database(), current_setting('work_mem') AS work_mem;

In shop, reporting received work_mem 32MB (source database user) and lock_timeout 2s (source database); in postgres it received 64MB (source user) and no lock timeout. The manual gives the order: a setting for the role in the database overrides one for the role, which overrides one for the database, and all of them override the configuration files. Step through the login to shop:

Interactive · stage by stage Where the settings of a new session come from Step through the reporting role's login to the shop database and watch each level replace the value before it.
Where the settings of a new session come from, step 7 of 7. Showing Built-in default, postgresql.conf, postgresql.auto.conf, Server command line, The database (lock_timeout 2s), The role (work_mem 64MB), The role in the database (work_mem 32MB), The session (32MB and 2s at login). In focus: The session.Built-in defaultwork_mem 4MB, no timeoutpostgresql.confread at start and reloadpostgresql.auto.confwritten by ALTER SYSTEMServer command linepostgres -c name=valueThe databaselock_timeout 2sThe rolework_mem 64MBThe role in the databasework_mem 32MBThe session32MB and 2s at login

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Built into the server. Every setting starts from the value compiled into PostgreSQL 18. For work_mem that is 4MB and for lock_timeout 0, which means no limit.
  2. The configuration files. The server reads postgresql.conf, then postgresql.auto.conf, where ALTER SYSTEM writes. A value in the second file wins over the first. On the lesson's server neither file sets work_mem or lock_timeout.
  3. The command line. Values given on the postgres command line with -c override both files, so they cannot be changed without a restart. This server was started without any.
  4. The database. The reporting role logs in to shop. ALTER DATABASE shop SET lock_timeout = '2s' applies now, at the start of the session, so lock_timeout becomes 2s.
  5. The role. ALTER ROLE reporting SET work_mem = '64MB' applies next and replaces the built-in 4MB. A setting for a role overrides one for a database.
  6. The role in this database. A setting for this role in this database is the most specific default, so work_mem becomes 32MB. pg_settings reports its source as database user, and lock_timeout's as database.
  7. The session itself. The session starts with work_mem 32MB and lock_timeout 2s, as the recorded run printed. A SET in the session replaces a value until the session ends, and SET LOCAL until the transaction ends.

The last run shows a trap. A superuser in shop ran SET ROLE reporting and still had work_mem 4MB: stored defaults apply only at login, and SET ROLE changes the privileges of a session, not its settings.

Which level fits depends on who needs the value and for how long. Answer two questions about a change you need to make:

Interactive · answer and compare Where should this setting change? Answer two questions about the change you need, and the table rules out the levels that would reach too far or end too soon.
Who needs the new value?
Must the value stay after the session that sets it ends?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Six places to change a setting, against what a change there reaches and how long it lasts
Where Reaches only what needs it Applies without a restart Outlives the session
The whole server Statement: ALTER SYSTEM SET Poor every session of every database and role Mixed a reload for most settings, a restart for postmaster ones such as shared_buffers Good kept in postgresql.auto.conf until RESET
One database Statement: ALTER DATABASE … SET Mixed every role that logs in to that database Good new sessions of the database, at login Good kept in pg_db_role_setting
One role Statement: ALTER ROLE … SET Mixed that role in every database Good new logins of the role; SET ROLE does not apply it Good kept in pg_db_role_setting
One role in one database Statement: ALTER ROLE … IN DATABASE … SET Good one role in one database: the narrowest stored default Good new logins of the role to that database Good kept in pg_db_role_setting
This session Statement: SET Good this session only Good at once, for user and superuser settings Poor gone when the session ends or on RESET
This transaction Statement: SET LOCAL Good this transaction only Good at once Poor gone at COMMIT or ROLLBACK, as the recorded 64MB was
How the answers decide
  • “Who needs the new value?” Every session of the server: rules out One database, One role, One role in one database, This session and This transaction. Those levels reach only some sessions; a value for the whole server belongs in the configuration files.
  • “Who needs the new value?” Every role in one database: rules out The whole server, One role, This session and This transaction and adds a caveat to One role in one database. The database's own default reaches every role there and nothing else; a role in that database covers one role only.
  • “Who needs the new value?” One role, wherever it logs in: rules out The whole server, One database, This session and This transaction and adds a caveat to One role in one database. The role's default follows it into every database; tie it to one database only if the need is limited to it.
  • “Who needs the new value?” One job that I run now: rules out The whole server, One database, One role and One role in one database. A stored default would change every later session too; SET or SET LOCAL changes the job and nothing else.
  • “Must the value stay after the session that sets it ends?” Yes, for every new session: rules out This session and This transaction. A SET value ends with its session, and a SET LOCAL value with its transaction.
  • “Must the value stay after the session that sets it ends?” No, only while this job runs: rules out The whole server, One database, One role and One role in one database. A stored default stays until someone resets it, long after the job that needed it.

In short: a value for everyone goes in the configuration files, a value for one database or one role in its stored default, and a value for one job in SET LOCAL inside that job’s transaction.

Checkpoint

Check: Database and role defaults

  1. Question 1 of 2 ALTER ROLE reporting SET work_mem = '64MB' and ALTER ROLE reporting IN DATABASE shop SET work_mem = '32MB' are both stored. reporting logs in to shop. What is its work_mem?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 32MB, with source database user

    The manual's order: database-role settings override role settings, which override database settings. The recorded login to shop received 32MB, and to postgres 64MB.

  2. Question 2 of 2 A superuser connected to shop runs SET ROLE reporting to test the reports with reporting's settings. Is the test valid?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: No; SET ROLE changes the privileges only, so work_mem stays at the session's value, 4MB in the run

    The ALTER ROLE page says it directly: "SET ROLE and SET SESSION AUTHORIZATION do not process role-specific variable settings". Test with psql -U reporting -d shop.

Trace any value back to the place that set it

When a value surprises you, the source column of pg_settings names the level that set it, and two more views take you to the exact place:

source says Where to look next
default Nothing set it: the built-in value applies
configuration file sourcefile and sourceline in pg_settings, or every entry in pg_file_settings
command line The options the postmaster was started with, recorded in postmaster.opts
database, user, database user pg_db_role_setting, joined to pg_database and pg_roles as levels.sql does
session A SET in this session; RESET brings back the value the session started with

Two checks belong after every configuration change: SELECT name, setting, pending_restart FROM pg_settings WHERE pending_restart lists the values waiting for a restart, and SELECT * FROM pg_file_settings WHERE error IS NOT NULL lists the entries that failed, such as a misspelt name or a value out of range. A reload skips an invalid value and only logs it, and a misspelt name or a syntax error makes it apply no setting from the file at all, so without that second check the change may never have happened.

Key idea

Ask the server where a value came from before you change it: pg_settings names the level and pending_restart, pg_file_settings the file and line, and pg_db_role_setting the database and role defaults.

Checkpoint

Check: Tracing a value to its source

  1. Question 1 of 2 pg_settings reports source 'database user' for work_mem. Where was the value set?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: By ALTER ROLE … IN DATABASE … SET, for this role in this database

    The recorded whoami.sql showed work_mem from database user and lock_timeout from database. pg_db_role_setting, joined to pg_roles and pg_database, lists them.

  2. Question 2 of 2 You add work_meme = '64MB' to postgresql.conf by mistake and reload. The reload reports nothing in your terminal. Where do you see the problem?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: In pg_file_settings, whose error column is not null for that entry, and in the server log

    A misspelt name or a syntax error makes a reload apply no setting from the file, an invalid value only that entry. Run SELECT * FROM pg_file_settings WHERE error IS NOT NULL after every change, and SELECT … WHERE pending_restart in pg_settings.

Practise: put each change at its level

Five requests reach a database team, and each belongs at a different level. Decide where each goes before you start the questions: the reporting role needs work_mem 256MB everywhere; one migration needs lock_timeout 2s; every session of the analytics database should time out statements after 30 seconds; the server needs shared_buffers raised to 4GB; and one script must log every statement it runs. The self-check then asks about the same decisions:

Practice that adapts to you

Practise this lesson

11 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 11 Without -U, what does initdb name the cluster's first superuser?

    Choose one answer.

    Show the answer: Question 1 of 11

    Answer: After the operating-system user who runs initdb

    The recorded command passed -U postgres and --pwfile, so the superuser is postgres with a password read from a file, never typed on the command line.

  2. Question 2 of 11 Which of these are fixed when initdb creates the cluster, so that changing them later means a new cluster or a new database?

    Choose every answer that is right.

    Show the answer: Question 2 of 11

    Answer:

    • The encoding of an existing database
    • The WAL segment size

    initdb's choices of WAL segment size, checksums, encoding and default locale shape everything after them, which is why the lesson makes them on purpose.

  3. Question 3 of 11 A colleague's install script runs initdb with --auth=trust on a shared build machine "to keep things simple". What do you say?

    Choose one answer.

    Show the answer: Question 3 of 11

    Answer: Any local user could then connect as any role, superuser included; use scram-sha-256 with a password file

    trust skips authentication entirely. A password from a file keeps it off the command line and out of shell history.

  4. Question 4 of 11 What does the context sighup mean for a setting?

    Choose one answer.

    Show the answer: Question 4 of 11

    Answer: It changes in the configuration files and takes effect at the next reload, for every session

    104 of the server's settings had context sighup in the recorded run, among them autovacuum and log_line_prefix.

  5. Question 5 of 11 One script must log every statement it runs, through log_min_duration_statement = 0. Its role is not a superuser. What decides whether it can SET that?

    Choose one answer.

    Show the answer: Question 5 of 11

    Answer: The setting's context, superuser, so a superuser must grant SET on it to the role, or set it as a stored default

    log_min_duration_statement had context superuser in the recorded run. GRANT SET ON PARAMETER, or ALTER ROLE … SET by a superuser, gives one role that value.

  6. Question 6 of 11 postgresql.conf says work_mem = '16MB' and postgresql.auto.conf says work_mem = '8MB'. After a reload, which applies?

    Choose one answer.

    Show the answer: Question 6 of 11

    Answer: 8MB, because postgresql.auto.conf is read after postgresql.conf and overrides it

    pg_file_settings shows both entries with their file and line, and which one is applied.

  7. Question 7 of 11 You must restart a production server for shared_buffers now, and long-running sessions are connected. Which stop mode does pg_ctl restart use by default, and what does it do to them?

    Choose one answer.

    Show the answer: Question 7 of 11

    Answer: Fast: it rolls back their transactions and disconnects them, then shuts down cleanly

    pg_ctl stop and restart default to fast. Plan a restart for a quiet moment and warn the users of long sessions.

  8. Question 8 of 11 One migration needs lock_timeout = '2s' so it gives up instead of queueing behind a long query. Where do you set it?

    Choose one answer.

    Show the answer: Question 8 of 11

    Answer: In the migration's own session or transaction, with SET or SET LOCAL

    A value for one job belongs in that job's session or transaction; the table of levels rules out the stored defaults for a one-off need.

  9. Question 9 of 11 Every session of the analytics database should stop statements after 30 seconds, but the role that runs nightly loads there needs no limit. Which pair of statements does it?

    Choose one answer.

    Show the answer: Question 9 of 11

    Answer: ALTER DATABASE analytics SET statement_timeout = '30s' and ALTER ROLE loader IN DATABASE analytics SET statement_timeout = 0

    Database-role settings override role settings, which override database settings, so the narrowest stored default carves out the exception.

  10. Question 10 of 11 Which source does pg_settings report for work_mem right after SET work_mem = '16MB'?

    Choose one answer.

    Show the answer: Question 10 of 11

    Answer: session

    RESET work_mem returned the session to the value it started with, 4MB in the recorded run.

  11. Question 11 of 11 postgresql.conf says shared_buffers = 4GB, the server was restarted, yet SHOW shared_buffers says 2GB. Which check finds the cause fastest?

    Choose one answer.

    Show the answer: Question 11 of 11

    Answer: SELECT source, sourcefile, sourceline FROM pg_settings WHERE name = 'shared_buffers', then pg_file_settings for every entry of it

    The last entry read wins, postgresql.auto.conf is read after postgresql.conf, and the command line overrides both; the views show which of them set the value.

Interview questions

Warm-up (fresher): What is the difference between reloading and restarting PostgreSQL, and how do you know which one a change needs?

A reload, pg_ctl reload or SELECT pg_reload_conf(), sends the postmaster a SIGHUP signal: it reads postgresql.conf, postgresql.auto.conf and pg_hba.conf again and forwards the signal to its child processes, and every connection stays open. A restart stops the server, ending every session, and starts it again. The context column of pg_settings tells you which a setting needs: postmaster settings such as shared_buffers and max_connections change only with a restart, sighup settings with a reload, and user or superuser settings can also be changed inside a session with SET. After a reload, pending_restart in pg_settings marks the changed values that are still waiting for a restart.

Two scenarios go further: a team that wants to put every change in ALTER SYSTEM, and a value in postgresql.conf that the running server does not show. Open them below for what a strong answer covers first and what an interviewer asks next:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A team wants one rule: every configuration change is made with ALTER SYSTEM and a reload. This week they need work_mem 256MB for the reporting role, lock_timeout 2s for one migration and statement_timeout 30s in the analytics database. What goes wrong with their rule, and what do you do instead?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    After an incident you find shared_buffers = 32GB in postgresql.conf, but SHOW shared_buffers on the running server says 8GB. Explain the possible causes and how you prove which one it is without guessing.

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Which choices does initdb fix that are hard to change later?

    Data page checksums (on by default since PostgreSQL 18), the encoding, the default locale and its provider, and the WAL segment size. A database also keeps its encoding and locale for life.

  2. What does a setting's context in pg_settings tell you?

    How a change takes effect: postmaster needs a restart, sighup needs a reload, user and superuser can be changed with SET in a session, and internal is fixed at build or cluster creation.

  3. What does ALTER SYSTEM do, and what are its limits?

    It writes the value into postgresql.auto.conf, which the server reads after postgresql.conf, so its values win. The manual forbids it in a transaction, since no ROLLBACK could take a written file back, and the new value applies only after a reload, or a restart for postmaster settings.

  4. Where does a value for one report or migration belong, and what is the precedence order?

    In SET LOCAL inside that job's transaction. Precedence: role in a database overrides role, which overrides database, which overrides the config files. Stored defaults apply only at login, never on SET ROLE.

  5. What is the difference between reloading and restarting PostgreSQL?

    A reload (pg_ctl reload or pg_reload_conf) sends SIGHUP to re-read config files with connections kept open. A restart ends every session. pending_restart in pg_settings marks changed postmaster settings still waiting.

  6. How do you trace a surprising setting back to where it was set?

    pg_settings names the source level and pending_restart; pg_file_settings gives the file, line and any errors; pg_db_role_setting holds the database and role defaults.

Key takeaways

  • initdb creates the cluster and fixes choices that are hard to change later: data checksums (on by default since PostgreSQL 18), the encoding, the default locale and its provider, and the WAL segment size.
  • The context of a setting in pg_settings says how a change takes effect: a restart for postmaster, a reload for sighup, and a SET in a session for user and superuser settings.
  • ALTER SYSTEM writes postgresql.auto.conf, which overrides postgresql.conf; it cannot run in a transaction block, and nothing changes until a reload or, for postmaster settings, a restart.
  • A setting for a role in a database overrides one for the role, which overrides one for the database; they apply only when a session starts, never on SET ROLE.
  • SET LOCAL changes a value for one transaction, the right place for the needs of a single report or migration.
  • pg_settings names the source of every value, pg_file_settings the file, line and errors of every entry, and pending_restart the changes still waiting for a restart.

Terms in this lesson

database cluster
One PostgreSQL data directory and the server that runs it, holding any number of databases that share its settings, roles and write-ahead log. initdb creates it.
postgresql.auto.conf
The configuration file in the data directory that ALTER SYSTEM writes. The server reads it after postgresql.conf, so a value set there overrides the same setting in postgresql.conf.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.