From 2995b3825e141887018569a48381b498baef1573 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sun, 17 Nov 2024 19:46:54 +0100 Subject: [PATCH] nixos/pgbouncer: rework RFC42 integration Commit bfb9d1825d545d96278db84d43e0b2a529775089 added RFC42 support which is a good thing in general, but this implementation has the following flaws: * `services.pgbouncer.logFile` was not renamed to `[...].log_file`, but to `[...].logfile`. Also the use of `mkRenamedOptionModule` is inappropriate here because the two options are not equivalent: the old option took a path relative to the home directory, the new an absolute path. * Using `mkRenamedOptionModule` with options that don't exist (but are keys in a freeform attr-set or an `attrsOf X`), you get the following error when referencing an option you didn't declare: error: evaluation aborted with the following error message: 'Renaming error: option `services.pgbouncer.settings.pgbouncer.listen_port' does not exist.' This error is pretty bad because it's not actionable for an end-user of the module. A possible use-case is doing networking.firewall.allowedTCPPorts = [ config.services.pgbouncer.listenPort ]; without specifying a custom listen port. This is an example of why you want to keep options, they already contain defaults and you can re-use those defaults in other parts of your system configuration. I decided to re-add a bunch of options where I figured that it's either useful to be able to address those in the NixOS configuration or having documentation directly in the options' reference in the NixOS manual. I didn't add all options, I'll leave that to the maintainers of pgbouncer. --- .../modules/services/databases/pgbouncer.nix | 188 +++++++++++++++++- 1 file changed, 184 insertions(+), 4 deletions(-) diff --git a/nixos/modules/services/databases/pgbouncer.nix b/nixos/modules/services/databases/pgbouncer.nix index 7eafa502eb0c..36130d66c685 100644 --- a/nixos/modules/services/databases/pgbouncer.nix +++ b/nixos/modules/services/databases/pgbouncer.nix @@ -3,14 +3,20 @@ let cfg = config.services.pgbouncer; settingsFormat = pkgs.formats.ini { }; - configFile = settingsFormat.generate "pgbouncer.ini" cfg.settings; + configFile = settingsFormat.generate "pgbouncer.ini" + (lib.filterAttrsRecursive (_: v: v != null) cfg.settings); configPath = "pgbouncer/pgbouncer.ini"; in { imports = [ - (lib.mkRenamedOptionModule + (lib.mkRemovedOptionModule [ "services" "pgbouncer" "logFile" ] - [ "services" "pgbouncer" "settings" "pgbouncer" "log_file" ]) + '' + `services.pgbouncer.logFile` has been removed, use `services.pgbouncer.settings.pgbouncer.logfile` + instead. + Please note that the new option expects an absolute path + whereas the old option accepted paths relative to pgbouncer's home dir. + '') (lib.mkRenamedOptionModule [ "services" "pgbouncer" "listenAddress" ] [ "services" "pgbouncer" "settings" "pgbouncer" "listen_addr" ]) @@ -128,7 +134,181 @@ in }; settings = lib.mkOption { - type = settingsFormat.type; + type = lib.types.submodule { + freeformType = settingsFormat.type; + options = { + pgbouncer = { + listen_port = lib.mkOption { + type = lib.types.port; + default = 6432; + description = '' + Which port to listen on. Applies to both TCP and Unix sockets. + ''; + }; + + listen_addr = lib.mkOption { + type = lib.types.nullOr lib.types.commas; + example = "*"; + default = null; + description = '' + Specifies a list (comma-separated) of addresses where to listen for TCP connections. + You may also use * meaning “listen on all addresses”. + When not set, only Unix socket connections are accepted. + + Addresses can be specified numerically (IPv4/IPv6) or by name. + ''; + }; + + pool_mode = lib.mkOption { + type = lib.types.enum [ "session" "transaction" "statement" ]; + default = "session"; + description = '' + Specifies when a server connection can be reused by other clients. + + session + Server is released back to pool after client disconnects. Default. + transaction + Server is released back to pool after transaction finishes. + statement + Server is released back to pool after query finishes. + Transactions spanning multiple statements are disallowed in this mode. + ''; + }; + + max_client_conn = lib.mkOption { + type = lib.types.int; + default = 100; + description = '' + Maximum number of client connections allowed. + + When this setting is increased, then the file descriptor limits in the operating system + might also have to be increased. Note that the number of file descriptors potentially + used is more than maxClientConn. If each user connects under its own user name to the server, + the theoretical maximum used is: + maxClientConn + (max pool_size * total databases * total users) + + If a database user is specified in the connection string (all users connect under the same user name), + the theoretical maximum is: + maxClientConn + (max pool_size * total databases) + + The theoretical maximum should never be reached, unless somebody deliberately crafts a special load for it. + Still, it means you should set the number of file descriptors to a safely high number. + ''; + }; + + default_pool_size = lib.mkOption { + type = lib.types.int; + default = 20; + description = '' + How many server connections to allow per user/database pair. + Can be overridden in the per-database configuration. + ''; + }; + + max_db_connections = lib.mkOption { + type = lib.types.int; + default = 0; + description = '' + Do not allow more than this many server connections per database (regardless of user). + This considers the PgBouncer database that the client has connected to, + not the PostgreSQL database of the outgoing connection. + + This can also be set per database in the [databases] section. + + Note that when you hit the limit, closing a client connection to one pool will + not immediately allow a server connection to be established for another pool, + because the server connection for the first pool is still open. + Once the server connection closes (due to idle timeout), + a new server connection will immediately be opened for the waiting pool. + + 0 = unlimited + ''; + }; + + max_user_connections = lib.mkOption { + type = lib.types.int; + default = 0; + description = '' + Do not allow more than this many server connections per user (regardless of database). + This considers the PgBouncer user that is associated with a pool, + which is either the user specified for the server connection + or in absence of that the user the client has connected as. + + This can also be set per user in the [users] section. + + Note that when you hit the limit, closing a client connection to one pool + will not immediately allow a server connection to be established for another pool, + because the server connection for the first pool is still open. + Once the server connection closes (due to idle timeout), a new server connection + will immediately be opened for the waiting pool. + + 0 = unlimited + ''; + }; + + ignore_startup_parameters = lib.mkOption { + type = lib.types.nullOr lib.types.commas; + example = "extra_float_digits"; + default = null; + description = '' + By default, PgBouncer allows only parameters it can keep track of in startup packets: + client_encoding, datestyle, timezone and standard_conforming_strings. + + All others parameters will raise an error. + To allow others parameters, they can be specified here, so that PgBouncer knows that + they are handled by the admin and it can ignore them. + + If you need to specify multiple values, use a comma-separated list. + + IMPORTANT: When using prometheus-pgbouncer-exporter, you need: + extra_float_digits + + ''; + }; + }; + databases = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; + example = { + exampledb = "host=/run/postgresql/ port=5432 auth_user=exampleuser dbname=exampledb sslmode=require"; + bardb = "host=localhost dbname=bazdb"; + foodb = "host=host1.example.com port=5432"; + }; + description = '' + Detailed information about PostgreSQL database definitions: + + ''; + }; + users = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; + example = { + user1 = "pool_mode=session"; + }; + description = '' + Optional. + + Detailed information about PostgreSQL user definitions: + + ''; + }; + + peers = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; + example = { + "1" = "host=host1.example.com"; + "2" = "host=/tmp/pgbouncer-2 port=5555"; + }; + description = '' + Optional. + + Detailed information about PostgreSQL database definitions: + + ''; + }; + }; + }; default = { }; description = '' Configuration for PgBouncer, see