DEV Community

Cover image for Cleanly modifying PostgreSQL configurations
Bolaji Wahab
Bolaji Wahab

Posted on • Edited on

Cleanly modifying PostgreSQL configurations

PostgreSQL has numerous configurations. Making changes to these configurations could occasionally result in issues or even stop the server from starting.
Additionally, a config might have been modified but yet to be applied. This may easily result in a scenario in which we modify a config, reload or restart the server, and then encounter a problem unrelated to our modification.

But the good news is that PostgreSQL provided an infrastructure to check if a config has an error so that it may be fixed before we reload or restart the server.

Ways of adjusting PostgreSQL configs

  • Editing the config files
  • Using ALTER SYSTEM

Checking for errors in the config

The SQL query below can be used to check for configuration errors

 SELECT name, sourcefile, sourceline, setting, error FROM pg_catalog.pg_file_settings WHERE error IS NOT NULL;
 name |                    sourcefile                     | sourceline | setting |                error
------+---------------------------------------------------+------------+---------+--------------------------------------
 test | /usr/local/etc/postgresql/14/main/postgresql.conf |          2 | test    | unrecognized configuration parameter
(1 row)

Time: 2.354 ms
Enter fullscreen mode Exit fullscreen mode

Let us try reloading the server. We get this info in the LOG:

2022-07-22 16:00:53.190 CEST [4317] LOG:  received SIGHUP, reloading configuration files
2022-07-22 16:00:53.190 CEST [4317] LOG:  unrecognized configuration parameter "test" in file "/usr/local/etc/postgresql/14/main/postgresql.conf" line 2
2022-07-22 16:00:53.191 CEST [4317] LOG:  configuration file "/usr/local/etc/postgresql/14/main/postgresql.conf" contains errors; no changes were applied
Enter fullscreen mode Exit fullscreen mode

Let us try restarting the server. We got the below error and the server could not be started.

2022-07-22 14:03:45.281 GMT [4940] LOG:  unrecognized configuration parameter "test" in file "/usr/local/etc/postgresql/14/main/postgresql.conf" line 2
2022-07-22 14:03:45.282 GMT [4940] FATAL:  configuration file "/usr/local/etc/postgresql/14/main/postgresql.conf" contains errors
pg_ctl: could not start server
Examine the log output.
Enter fullscreen mode Exit fullscreen mode

The issues can be quickly fixed because we can check the state of the configuration before reloading or restarting.

To avoid surprises, this can also be implemented into a configuration management system.

Checking for pending reload and pending restart

It is wise to confirm the current state of the server before making any configuration changes. This guarantees that we begin in a clean condition and helps prevent cascade problems.

Consider a scenario in which one of the DBAs on your team changed random page cost but the change is not yet effective. You make your modification and reload or restart the server. After a few days, your queries began to perform poorly. You were questioned about the adjustment you made and even requested to reverse it, but there was still no relief.
Investigating such issues can be tiresome and time-consuming; it is better to be safe than sorry.

We can easily check for pending reload and restart with this SQL query:

WITH current_settings AS (
    SELECT name
         , setting
         , CASE
             WHEN unit IN ('kB', 'MB')
              AND setting <> '-1'
               THEN pg_catalog.pg_size_pretty(pg_catalog.pg_size_bytes(setting) * pg_catalog.pg_size_bytes(format('1%1$s', unit)))
             WHEN unit = '8kB'
              AND setting <> '-1'
               THEN pg_catalog.pg_size_pretty(pg_catalog.pg_size_bytes(setting) * pg_catalog.pg_size_bytes(unit))
             WHEN unit = 'B'
              AND setting <> '-1'
               THEN pg_catalog.pg_size_pretty(pg_catalog.pg_size_bytes(setting))
             WHEN unit IN ('ms', 's', 'min')
              AND setting <> '-1'
               THEN format('%1$s %2$s', setting, unit)
             WHEN setting = '-1'
               THEN setting
             ELSE setting
           END AS current_setting
         , unit
         , context
      FROM pg_catalog.pg_settings
)
, file_settings AS (
    SELECT row_number() OVER (PARTITION BY pf.name ORDER BY pf.seqno DESC) AS rn
         , pf.name
        -- When autovacuum_work_mem is greater than -1 but less than the min_val of maintenance_work_mem which is 1024 kB,
        -- PostgreSQL sets autovacuum_work_mem to 1024 kB
         , CASE
             WHEN pf.name = 'autovacuum_work_mem'
              AND pf.setting <> '-1'
              AND (
                    pg_catalog.pg_size_bytes(pf.setting)
                    *
                    CASE
                      WHEN pf.setting ~ '\d+\s*[a-zA-Z]?(B)'
                        THEN 1
                      ELSE pg_catalog.pg_size_bytes(format('1%1$s', cs.unit))
                    END
                  ) < pg_catalog.pg_size_bytes('1024 kB')
               THEN '1024 kB'
             WHEN cs.unit IN ('kB', 'MB')
              AND pf.setting <> '-1'
               THEN pg_catalog.pg_size_pretty(
                        pg_catalog.pg_size_bytes(pf.setting)
                        *
                        CASE
                          WHEN pf.setting ~ '\d+\s*[a-zA-Z]?(B)'
                            THEN 1
                          ELSE pg_catalog.pg_size_bytes(format('1%1$s', cs.unit))
                        END
                    )
             WHEN unit = '8kB'
              AND pf.setting <> '-1'
               THEN pg_catalog.pg_size_pretty(
                        pg_catalog.pg_size_bytes(pf.setting)
                        *
                        CASE
                          WHEN pf.setting ~ '\d+\s*[a-zA-Z]?(B)'
                            THEN 1
                          ELSE pg_catalog.pg_size_bytes(format('1%1$s', cs.unit))
                        END
                    )
             WHEN cs.unit = 'B'
              AND pf.setting <> '-1'
               THEN pg_catalog.pg_size_pretty(
                        pg_catalog.pg_size_bytes(pf.setting)
                        *
                        CASE
                          WHEN pf.setting ~ '\d+\s*[a-zA-Z]?(B)'
                            THEN 1
                          ELSE pg_catalog.pg_size_bytes(format('1%1$s', cs.unit))
                        END
                    )
             WHEN cs.unit = 'ms'
              AND pf.setting <> '-1'
               THEN format(
                        '%1$s %2$s'
                      , CAST(
                            floor(
                                EXTRACT(
                                    EPOCH FROM CAST(
                                        format(
                                            '%1$s%2$s'
                                          , pf.setting
                                          , CASE
                                              WHEN pf.setting ~ '\d+\s*([a-zA-Z]+)'
                                                THEN ''
                                              ELSE cs.unit
                                            END
                                        ) AS interval
                                    )
                                ) * 1000
                            ) AS text
                        )
                      , cs.unit
                    )
             WHEN cs.unit = 's'
              AND pf.setting <> '-1'
               THEN format(
                        '%1$s %2$s'
                      , CAST(
                            floor(
                                EXTRACT(
                                    EPOCH FROM CAST(
                                        format(
                                            '%1$s%2$s'
                                          , pf.setting
                                          , CASE
                                              WHEN pf.setting ~ '\d+\s*([a-zA-Z]+)'
                                                THEN ''
                                              ELSE cs.unit
                                            END
                                        ) AS interval
                                    )
                                )
                            ) AS text
                        )
                      , cs.unit
                    )
             WHEN cs.unit = 'min'
              AND pf.setting <> '-1'
               THEN format(
                        '%1$s %2$s'
                      , CAST(
                            floor(
                                EXTRACT(
                                    EPOCH FROM CAST(
                                        format(
                                            '%1$s%2$s'
                                          , pf.setting
                                          , CASE
                                              WHEN pf.setting ~ '\d+\s*([a-zA-Z]+)'
                                                THEN ''
                                              ELSE cs.unit
                                            END
                                        ) AS interval
                                    )
                                ) / 60
                            ) AS text
                        )
                      , cs.unit
                    )
             WHEN pf.setting = '-1'
               THEN pf.setting
             ELSE pf.setting
           END AS file_setting
         , pf.sourcefile
         , pf.sourceline
         , pf.error
      FROM current_settings AS cs
     INNER JOIN pg_catalog.pg_file_settings AS pf
        ON pf.name = cs.name
)
SELECT cs.name
     , cs.current_setting
     , fs.file_setting AS pending_setting
     , cs.context
     , fs.sourcefile
     , fs.sourceline
     , fs.error
     , CASE
         WHEN cs.current_setting <> fs.file_setting
          AND cs.context = 'sighup'
           THEN TRUE
         ELSE FALSE
       END AS pending_reload
     , CASE
         WHEN cs.current_setting <> fs.file_setting
          AND cs.context = 'postmaster'
           THEN TRUE
         ELSE FALSE
       END AS pending_restart
  FROM current_settings AS cs
 INNER JOIN file_settings AS fs
    ON cs.name = fs.name
 WHERE fs.rn = 1
   AND cs.current_setting <> fs.file_setting
 ORDER BY pending_reload DESC, pending_restart DESC;
Enter fullscreen mode Exit fullscreen mode

Sample output:

        name         | current_setting | pending_setting |  context   |                         sourcefile                         | sourceline | error  | pending_reload | pending_restart
---------------------+-----------------+-----------------+------------+------------------------------------------------------------+------------+--------+----------------+-----------------
 archive_command     | (disabled)      | /bin/true       | sighup     | /usr/local/var/lib/postgresql/14/main/postgresql.auto.conf |         28 | <null> | true           | false
 autovacuum_work_mem | 10 MB           | 1024 kB         | sighup     | /usr/local/var/lib/postgresql/14/main/postgresql.auto.conf |         20 | <null> | true           | false
 max_wal_size        | 1024 MB         | 10 GB           | sighup     | /usr/local/var/lib/postgresql/14/main/postgresql.auto.conf |         26 | <null> | true           | false
 archive_mode        | off             | on              | postmaster | /usr/local/var/lib/postgresql/14/main/postgresql.auto.conf |         27 | <null> | false            | true
 shared_buffers      | 160 MB          | 1024 MB         | postmaster | /usr/local/var/lib/postgresql/14/main/postgresql.auto.conf |         25 | <null> | false            | true
(5 rows)

Time: 8.198 ms
Enter fullscreen mode Exit fullscreen mode

To avoid surprises, this can also be implemented into a configuration management system.

Top comments (1)

Collapse
 
bolajiwahab profile image
Bolaji Wahab

Please note the above query does not work when setting a config to a default value or resetting a config. The reason according to the documentation:
Setting a parameter to DEFAULT, or using the RESET variant, removes that configuration entry from the postgresql.auto.conf file