Adjusting Operating System Resources
- Increase the Limit of Open Files
Each PostgreSQL connection consumes a file descriptor. Adjust the limits in /etc/security/limits.conf:
|
|
- Modify Kernel Parameters
Increase the maximum number of network connections and system semaphores in /etc/sysctl.conf:
|
|
- Apply the Changes
|
|
ETCD Installation and Configuration
wget https://github.com/etcd-io/etcd/releases/download/v3.5.0/etcd-v3.5.0-linux-amd64.tar.gz
Once downloaded unzip and copy binaries to your /usr/bin
|
|
|
|
- Configure and run 3 node ETCD Cluster:
- Create etcd user and group for etcd binaries to run:
|
|
- Create two directories(data and configuration)
|
|
- Login using etcd user and create .bash_profile file with below content
|
|
- Create Service etcd in /etc/systemd/system/etcd.service, replace IP addresses with your corresponding machine IPs
|
|
- Once Service created enable the service and start it on all three servers
|
|
- You can check cluster working by issuing following commands:
|
|
- To check leader you can check endpoint status:
|
|
Note : By default etcd does not support v2 API, in case patroni fails to start with the api error, add –enable-v2 flag in etcd service
Troubleshooting
- Check connection to etcd:
|
|
- Failover: check if a node has the nofailover tag
|
|
Remove nofailover tag:
|
|
Patroni and Postgres Installation
|
|
- Install Patroni service. You need to install extra package required for connecting to etcd.
|
|
- Enable Patroni service
systemctl enable patroni
- Create configuration file and required directories for patroni:
|
|
- Create config file for patroni as below (/etc/patroni/patroni.yml)
touch /etc/patroni/patroni.yml
|
|
In the bootstrap section of your Patroni configuration, Patroni automatically creates the users defined under the users key when initializing the PostgreSQL cluster. You do not need to create these users manually before starting PostgreSQL; Patroni will handle it when the cluster starts.
- Cluster Initialization:
When Patroni runs for the first time, it creates the PostgreSQL cluster if it doesn’t already exist. During this process, it applies the configurations defined in the bootstrap section.
- User Creation:
The users defined under the users key in your configuration will be created automatically. If there are errors during Patroni startup, the users may not be created.
- User Verification:
Once the cluster is running, you can connect to PostgreSQL to verify that the users were created correctly:
|
|
This should display a list of users, including admin and replicator, that Patroni created during the initialization process.
4.1. Bootstrap Section The bootstrap section in Patroni is used to define how the PostgreSQL cluster is initialized and to set up the initial configuration, including user creation.
Creating a post_bootstrap Script
To automate additional tasks after the bootstrap process, you can create a post_bootstrap script. This script will run automatically after the initial cluster setup and can be used to create necessary users, set permissions, or perform other configurations.
Example post_bootstrap Script
Create a script, for example, named post_bootstrap.sh:
|
|
Modify your Patroni configuration:
|
|
Or directly over patroni configuration file:
|
|
• admin: This user is created during cluster initialization and has permissions to create roles and databases. It is an administrative user typically used for management tasks. • replicator: This user is also created during initialization and is specifically used for replication tasks. The replication option grants it permission to connect as a replication user.
4.2. PostgreSQL Section
|
|
• replication: This defines how the replicator user authenticates for replication. The password must match the one defined in the bootstrap section to ensure successful replication connections.
Password Storage
The credentials are stored at runtime in the pgpass file specified in the Patroni configuration. This ensures the replication user’s authentication details are available for automatic processes.
|
|
- Start Patroni
service patroni start
Repeat same procedure on all three nodes, for any issues you can set log.level
and log.traceback_level
to DEBUG.
Once all nodes are up and running you can check status of patroni cluster using patronictl utility.
patronictl -c /etc/patroni/patroni.yml list
Now patroni cluster is ready to use, you can start playing around and do some replication and failover tests.
After this we need to setup load balancer to point it to active (Leader) Postgres database. For this you need two HAProxy servers or if you are setting this on cloud you can use load balancers provided by cloud provider.
Troubleshooting
|
|
- Check health
|
|
Operations
- Apply Changes with Reload
If Patroni indicates that there is a pending_restart, you can apply the configuration changes by running the Patroni reload command.
|
|
- Replication status on the leader: On the leader node (psql01), check if it correctly detects psql03 as a replica:
|
|
- Check ports:
|
|
HAProxy: Install load balancer
After this we need to setup load balancer to point it to active (Leader) Postgres database. For this you need two HAProxy servers or if you are setting this on cloud you can use load balancers provided by cloud provider.
- Install HAProxy on both servers:
apt install haproxy
- Configure haproxy.cfg file to redirect all traffic to active postgres leader.
|
|
Note : Haproxy will check 8008 port of pgdb servers and if it returns 200 status then it will redirect all traffic to the leader. This 8008 port is configured in Patroni.
- Start haproxy on both nodes
service haproxy start
Once haproxy is started you can check status by hitting url http://haproxy1:7000 You can see all connections on haproxy:5432 will be redirected to pgdb1:5432, you can check if pgdb1 is the leader or not. Now try connecting to the cluster using haproxy host, it should get redirected to leader.
Setting up software watchdog on Linux
Watchdog devices are software or hardware mechanisms that will reset the whole system when they do not get a keepalive heartbeat within a specified timeframe. This adds an additional layer of fail safe in case usual Patroni split-brain protection mechanisms fail.
While the use of a watchdog mechanism with Patroni is optional, you shouldn’t really consider deploying a PostgreSQL HA environment in production without it.
Patroni will be the component interacting with the watchdog device. Since Patroni is run by the postgres user, we need to either set the permissions of the watchdog device open enough so the postgres user can write to it or make the device owned by postgres itself, which we consider a safer approach (as it is more restrictive).
Default Patroni configuration will try to use /dev/watchdog on Linux if it is accessible to Patroni. For most use cases using software watchdog built into the Linux kernel is secure enough.
- Installation
|
|
- Create service
|
|
- Apply
|
|
To enable software watchdog manually issue the following commands as root before starting Patroni:
modprobe softdog
|
|
Load at boot:
|
|
REMOVE ENTRY softdog in all blacklist (in order to start at boot… /lib/modprobe.d/blacklist_…
|
|
For testing it may be helpful to disable rebooting by adding soft_noboot=1
to the modprobe command line. In this case the watchdog will just log a line in kernel ring buffer, visible via dmesg.
Patroni will log information about the watchdog when it is successfully enabled.
|
|
Pgbouncer
It is useful to use a connection pool to handle large numbers of users without overwhelming PostgreSQL. PgBouncer can be configured to work with a Patroni cluster, and in fact, it is a common practice for managing connections to the PostgreSQL cluster. PgBouncer acts as a proxy and connection pooler that distributes client requests to the nodes of the cluster according to the configuration and logic of Patroni (i.e., to the leader or followers).
Configuring PgBouncer in a Patroni Cluster:
- Install PgBouncer on each node in the cluster On each node where Patroni manages a PostgreSQL instance, install PgBouncer.
|
|
- Configure pgbouncer.ini On each node, edit the PgBouncer configuration file (/etc/pgbouncer/pgbouncer.ini or equivalent). A typical configuration example for a Patroni cluster:
|
|
|
|
In this configuration:
- auth_user should be configured with a user that has access to the databases.
- Use transaction mode (pool_mode = transaction) to prevent connections from getting “stuck” during failovers or leader changes.
- Create the userlist.txt file
Define the users that PgBouncer will use to authenticate with PostgreSQL. For example, in /etc/pgbouncer/userlist.txt:
- Example:
|
|
Relationship between users and databases:
- When a client connects to PgBouncer requesting a specific database, PgBouncer verifies the provided user credentials against userlist.txt.
- If the credentials are correct, PgBouncer opens a connection to the PostgreSQL server using the username provided by the client.
- Configure Patroni to work with PgBouncer
Patroni needs to “inform” the load balancer (in this case, PgBouncer) about changes in node status. This is done through membership tags and custom scripts.
- Adjust the Patroni configuration in patroni.yml:
|
|
- Optional: Use health scripts to monitor which node should receive traffic:
- Set up a script in PgBouncer or a health check to redirect traffic to the leader.
- You can query the Patroni REST endpoint (http://:8008/) to check which node is the leader.
- Configure clients to connect to PgBouncer
Instead of connecting directly to PostgreSQL, configure your applications to point to PgBouncer. For example:
- Host:
- Port: 6432
- Database: mydb
- Load balancing between followers (Optional)
If you want to allow read-only connections to the followers of the cluster, you can configure multiple entries in pgbouncer.ini:
|
|
- Restart PgBouncer
|
|
Failover Scripts: To ensure a quick redirection after a failover in Patroni, it’s useful to integrate scripts that automatically update PgBouncer’s configuration.
PgBouncer on a dedicated node: In large implementations, it’s common to use a dedicated node for PgBouncer rather than installing it on the cluster nodes.
STATISTICS
PgBouncer’s virtual tables are not accessible through traditional SQL queries like you would in a PostgreSQL database. Instead, PgBouncer uses specific SHOW commands to gather information.
- SHOW POOLS;: Information about the connection pools.
- SHOW STATS;: General statistics about connections and pools.
- SHOW CLIENTS;: Information about client connections.
- SHOW USERS;: Users connected to PgBouncer.
- SHOW CONFIG;: Current configuration parameters of PgBouncer.
Connections
If you have PgBouncer configured and have also defined max_connections in Patroni, both configurations affect the system complementarily but independently.
Explanation of the behavior:
- max_connections in Patroni (PostgreSQL): This value limits the maximum number of connections PostgreSQL can accept directly, whether from PgBouncer or any external client. This is an absolute limit for PostgreSQL.
If max_connections = 200, PostgreSQL will not accept more than 200 active simultaneous connections, including those from PgBouncer.
- max_client_conn and default_pool_size in PgBouncer: These configurations control how PgBouncer handles client connections to PostgreSQL.
- max_client_conn: Defines how many connections PgBouncer can accept from clients (applications).
- default_pool_size: Defines how many connections PgBouncer keeps open with PostgreSQL for each database in the pool.
PgBouncer uses a pooling model, which means that multiple clients can share a single connection to PostgreSQL.
Which one takes precedence?
Both limits are important, but the effective limit is defined by max_connections in Patroni. PgBouncer can handle more clients (max_client_conn), but it can only open connections to PostgreSQL up to the limit defined in max_connections.
For example:
- If max_connections = 200 in Patroni and default_pool_size = 20 in PgBouncer, PgBouncer can only open 200 connections to PostgreSQL in total (if multiple databases are connected, the pool is distributed among them).
- If max_client_conn = 500 in PgBouncer, it can accept up to 500 client connections, but only 200 connections will be established to PostgreSQL at the same time.
How to optimize this configuration:
- Increase max_connections in Patroni if the server has enough capacity: Adjust the parameter in the patroni.yml file:
|
|
Restart Patroni to apply the changes.
- Configure default_pool_size in PgBouncer based on needs:
- Set a reasonable pool size based on the expected load.
- For example, if you expect 500 concurrent clients and have 200 available connections in PostgreSQL, a default_pool_size = 10 could be enough to distribute the load.
- Monitor connection usage: Use views like pg_stat_activity and PgBouncer statistics to ensure you’re not hitting the limits:
|
|
|
|
Conclusion:
The max_connections limit in Patroni takes precedence, as PostgreSQL cannot exceed this number of active connections. PgBouncer helps distribute and optimize client connections, but it must be properly configured to ensure it doesn’t exceed this limit. If you expect to handle many clients, it’s advisable to use PgBouncer with a high max_client_conn and adjust max_connections in Patroni according to available resources.
HAProxy vs PgBouncer
While HAProxy handles distributing connections between the nodes of your Patroni cluster, PgBouncer remains useful as a connection pool to optimize the management of connections to PostgreSQL.
Here’s how HAProxy and PgBouncer interact and how to handle this combination.
Roles of HAProxy and PgBouncer in this context:
- HAProxy:
- Acts as the network-level load balancer, distributing incoming connections across the Patroni cluster nodes.
- Determines which node to send traffic to based on the node’s status (leader or follower).
- PgBouncer:
- Not a load balancer per se, but a connection pool manager.
- Reduces the load on PostgreSQL by reusing existing connections, especially in applications that generate many short-lived or burst connections.
- Complements HAProxy by optimizing the number of active connections on the cluster nodes.
Is it necessary to use both?
Yes, in many cases. Using PgBouncer alongside HAProxy is a common practice in high-load systems because they address different issues:
- HAProxy balances traffic between nodes.
- PgBouncer manages connections to a node, reducing the impact of many concurrent connections.
Without PgBouncer, every connection passing through HAProxy reaches PostgreSQL directly, which could overload the max_connections on the server.
Recommended Architecture
- HAProxy balances client connections to PgBouncer.
- PgBouncer resides on each PostgreSQL node, optimizing connection usage on that node.
The flow would be:
Clients → HAProxy → PgBouncer (on each node) → PostgreSQL
This allows:
- HAProxy to determine the correct node for connections.
- PgBouncer to limit and optimize connections to PostgreSQL.
Configuration in a Combined Environment
- HAProxy Configuration
HAProxy is configured to route traffic to the leader or followers depending on usage:
- In the HAProxy configuration file (haproxy.cfg):
|
|
In this example, HAProxy sends traffic to the PgBouncer port (6432) on each node.
- PgBouncer Configuration
PgBouncer is configured to optimize connections on each PostgreSQL node.
- In the PgBouncer configuration file (pgbouncer.ini):
|
|
-
listen_port is the port where PgBouncer listens (matching the port configured in HAProxy).
-
pool_mode is recommended as transaction for Patroni clusters, as it avoids issues with leader changes.
- Ensuring Consistency in Failover
When a failover occurs in Patroni, HAProxy must automatically redirect traffic to the new leader. You can configure a health check in HAProxy to validate the leader node via Patroni’s REST endpoint:
|
|
This ensures that only the leader node receives write traffic.
What about the current error?
The “This connection has been closed” error you mentioned likely occurs because:
- The number of simultaneous connections in PostgreSQL (max_connections) is insufficient:
- Increase max_connections in Patroni to support more connections.
- PgBouncer will help optimize the use of these connections.
- The connection timeout is too short:
- Increase timeout values in HAProxy and PgBouncer to prevent connections from closing prematurely.
In HAProxy, you can adjust the timeout with:
|
|
In PgBouncer:
|
|
Conclusion
- Use PgBouncer to optimize connections at the node level.
- HAProxy remains the main load balancer between nodes.
- Adjust max_connections, timeouts, and pool sizes to support the load.
Application side Configuration:
As we have two HAProxy servers application should be configured in such a way that it should point to both servers, submit the request to available server and if application does not support such case then you need to set up virtual IP which will point to available HAProxy server.