{"id":35706,"date":"2019-10-31T22:05:50","date_gmt":"2019-10-31T19:05:50","guid":{"rendered":"https:\/\/prohoster.info\/blog\/otkazoustojchivyj-klaster-postgresql-patroni-opyt-vnedreniya\/"},"modified":"2026-05-18T20:58:46","modified_gmt":"2026-05-18T18:58:46","slug":"otkazoustojchivyj-klaster-postgresql-patroni-opyt-vnedreniya","status":"publish","type":"post","link":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/otkazoustojchivyj-klaster-postgresql-patroni-opyt-vnedreniya","title":{"rendered":"Fault-tolerant PostgreSQL cluster + Patroni. Implementation experience.","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>In this article, I will explain how we approached the issue of PostgreSQL fault tolerance, why it became important to us, and what the outcome was.<\/p>\n<p>We have a high-traffic service: 2.5 million users worldwide, with over 50K active users every day. Our servers are located in an Amazon region in Ireland: we constantly have more than 100 different servers in operation, of which almost 50 are database servers.<\/p>\n<p>The entire backend is a large monolithic stateful Java application that maintains a constant websocket connection with the client. When multiple users are working simultaneously on the same board, they all see changes in real time because we log every change in the database. We handle around 10K requests per second to our databases. At peak load in Redis, we write about 80-100K requests per second.<br \/>\n<img decoding=\"async\" style=\"display: block; margin: 0 auto;\" src=\"\/wp-content\/uploads\/2019\/06\/443f85815b0560fade7db5b639942935.png\" alt=\"Fault-tolerant PostgreSQL cluster + Patroni. Implementation experience.\" \/><br \/>\n<a rel=\"nofollow\" name=\"habracut\"><\/a><\/p>\n<h2>Why we switched from Redis to PostgreSQL<\/h2>\n<p>Initially, our service operated with Redis, a key-value store that keeps all data in memory. <a href=\"https:\/\/prohoster.info\/en\/server\/\">server<\/a>.<\/p>\n<p>Pros of Redis:<\/p>\n<ol>\n<li>High response speed, as everything is stored in memory;<\/li>\n<li>Convenience of backup and replication.<\/li>\n<\/ol>\n<p>Cons of Redis for us:<\/p>\n<ol>\n<li>No real transactions. We tried to simulate them at the application level. Unfortunately, this didn't always work well and required writing very complicated code.<\/li>\n<li>Data volume is limited by the amount of memory. As the amount of data increases, memory will grow, and eventually, we will hit the limitations of the selected instance type, which in AWS requires stopping our service to change instance types.<\/li>\n<li>We must constantly maintain a low latency level since we have a very high number of requests. The optimal level of latency for us is 17-20 ms. At levels of 30-40 ms, we experience long response times to requests from our application and service degradation. Unfortunately, this occurred in September 2018 when one of the Redis instances inexplicably experienced latency twice as high as usual. To resolve the issue, we halted the service in the middle of the working day for unscheduled maintenance and replaced the problematic Redis instance.<\/li>\n<li>It's easy to achieve data inconsistency even with minor coding errors and then spend a lot of time writing code to fix that data.<\/li>\n<\/ol>\n<p>We took the downsides into account and realized that we needed to move to something more convenient, with normal transactions and less dependence on latency. We conducted research, analyzed numerous options, and chose PostgreSQL.<\/p>\n<p>We have been migrating to the new database for 1.5 years and have only transferred a small portion of the data, so we are currently working simultaneously with Redis and PostgreSQL. More about the stages of migration and switching data between databases is detailed in <a href=\"https:\/\/habr.com\/ru\/company\/miro\/blog\/437826\/\" rel=\"nofollow\">my colleague's article<\/a>.<\/p>\n<p>When we first started migrating, our application was interacting directly with the database and making calls to the Redis and PostgreSQL masters. The PostgreSQL cluster consisted of a master and a replica with asynchronous replication. This is what the database interaction scheme looked like:<br \/>\n<img decoding=\"async\" style=\"display: block; margin: 0 auto;\" src=\"\/wp-content\/uploads\/2019\/06\/674dd77de4c8b48a946c9f64c0b2fdd1.png\" alt=\"Fault-tolerant PostgreSQL cluster + Patroni. Implementation experience.\" \/><\/p>\n<h2>Implementing PgBouncer<\/h2>\n<p>While we were migrating, the product was also evolving: the number of users and servers working with PostgreSQL increased, and we ran out of connections. PostgreSQL creates a separate process for each connection, consuming resources. The number of connections can be increased only up to a certain point; otherwise, there is a risk of suboptimal database performance. The ideal solution in this situation would be to choose a connection manager that would sit in front of the database.<\/p>\n<p>We had two options for the connection manager: Pgpool and PgBouncer. However, the first one does not support transactional mode when working with the database, so we chose PgBouncer.<\/p>\n<p>We set up the following interaction scheme: our application connects to one PgBouncer, behind which are the PostgreSQL masters, and each master has one replica with asynchronous replication.<br \/>\n<img decoding=\"async\" style=\"display: block; margin: 0 auto;\" src=\"\/wp-content\/uploads\/2019\/06\/dabcb7f6006520c4fec85fb925e8975e.png\" alt=\"Fault-tolerant PostgreSQL cluster + Patroni. Implementation experience.\" \/><\/p>\n<p>At the same time, we could not store the entire volume of data in PostgreSQL, and for us, the speed of working with the database was important, so we began sharding PostgreSQL at the application level. The scheme described above is relatively convenient for this: when adding a new shard, it is enough to update the PgBouncer configuration, and the application can immediately work with the new shard.<\/p>\n<h3>PgBouncer fault tolerance<\/h3>\n<p>This scheme worked until the only PgBouncer instance died. We are on AWS, where all instances are running on hardware that sometimes fails. In such cases, the instance simply moves to new hardware and starts working again. This also happened with PgBouncer, but it became unavailable. As a result of this failure, our service was down for 25 minutes. AWS recommends using redundancy on the user side for such situations, which we had not implemented at that time.<\/p>\n<p>After that, we seriously considered the fault tolerance of PgBouncer and PostgreSQL clusters because a similar situation could repeat with any instance in our AWS account.<\/p>\n<p>We built the fault tolerance scheme for PgBouncer as follows: all application servers connect to a Network Load Balancer, behind which are two PgBouncer instances. Each PgBouncer looks at the same master PostgreSQL of each shard. In the event of a repeat of the AWS instance failure, all traffic is redirected through the other PgBouncer. AWS ensures the fault tolerance of the Network Load Balancer.<\/p>\n<p>This scheme allows for easy addition of new PgBouncer servers.<br \/>\n<img decoding=\"async\" style=\"display: block; margin: 0 auto;\" src=\"\/wp-content\/uploads\/2019\/06\/2c1f1e7d9f7be4fdeb43858520d55e36.png\" alt=\"Fault-tolerant PostgreSQL cluster + Patroni. Implementation experience.\" \/><\/p>\n<h2>Creating a fault-tolerant PostgreSQL cluster<\/h2>\n<p>In solving this task, we considered different options: custom failover, repmgr, AWS RDS, Patroni.<\/p>\n<h3>Custom scripts<\/h3>\n<p>Can monitor the master\u2019s operation and, in case of its failure, promote the replica to master and update the PgBouncer configuration.<\/p>\n<p>The advantages of this approach lie in its maximum simplicity, as you write the scripts yourself and clearly understand how they work.<\/p>\n<p>Cons:<\/p>\n<ul>\n<li>The master may not have failed; instead, there could have been a network failure. The failover, unaware of this, will promote the replica to master, and the old master will continue to operate. As a result, we will have two servers acting as master and won\u2019t know which one has the latest data. This situation is also referred to as split-brain.<\/li>\n<li>We ended up without a replica. In our configuration, there is one master and one replica; after the switch, the replica is promoted to master, and we no longer have replicas, so we have to manually add a new replica.<\/li>\n<li>Additional monitoring of the failover process is needed; we have 12 PostgreSQL shards, which means we need to monitor 12 clusters. When increasing the number of shards, we must also remember to update the failover.<\/li>\n<\/ul>\n<p>A custom failover seems very complex and requires non-trivial support. With a single PostgreSQL cluster, this would be the simplest option, but it does not scale, so it is not suitable for us.<\/p>\n<h3>Repmgr<\/h3>\n<p>Replication Manager for PostgreSQL clusters, which is capable of managing PostgreSQL cluster operations. However, it does not have automatic failover 'out of the box', so you will need to write your own 'wrapper' on top of the existing solution. Thus, it may end up being even more complicated than with custom scripts, which is why we didn't even try Repmgr.<\/p>\n<h3>AWS RDS<\/h3>\n<p>It supports everything we need, can make backups, and supports connection pooling. It has automatic failover: when the master dies, the replica becomes the new master, and AWS updates the DNS record to point to the new master, while the replicas can be in different AZs.<\/p>\n<p>The downsides include the lack of fine-tuning options. For example, on our instances, we have limitations set for TCP connections, which unfortunately cannot be configured in RDS:<\/p>\n<pre><code class=\"python\">net.ipv4.tcp_keepalive_time=10\nnet.ipv4.tcp_keepalive_intvl=1\nnet.ipv4.tcp_keepalive_probes=5\nnet.ipv4.tcp_retries2=3\n<\/code><\/pre>\n<p>Furthermore, AWS RDS is nearly twice as expensive as the regular instance price, which was the main reason for rejecting this solution.<\/p>\n<h3>Patroni<\/h3>\n<p>This is a Python template for managing PostgreSQL with good documentation, automatic failover, and open-source code on GitHub.<\/p>\n<p>Pros of Patroni:<\/p>\n<ul>\n<li>Every configuration parameter is detailed, making it clear how everything works;<\/li>\n<li>Automatic failover works out of the box;<\/li>\n<li>Written in Python, and since we also write a lot in Python, it will be easier for us to handle issues and possibly even help with the project's development;<\/li>\n<li>Completely manages PostgreSQL, allows configuration changes to be applied across all nodes in the cluster, and if a restart is required to apply the new configuration, it can also be done with Patroni.<\/li>\n<\/ul>\n<p>Cons:<\/p>\n<ul>\n<li>The documentation does not clearly explain how to work with PgBouncer. Although it is difficult to call this a downside, because the task of Patroni is to manage PostgreSQL, and how connections will flow to Patroni is our problem;<\/li>\n<li>There are few examples of implementing Patroni at large scales, while there are many examples of starting from scratch.<\/li>\n<\/ul>\n<p>As a result, we chose Patroni for building a fault-tolerant cluster.<\/p>\n<h2>The process of implementing Patroni.<\/h2>\n<p>Before Patroni, we had 12 PostgreSQL shards configured with one master and one replica using asynchronous replication. The application servers accessed the databases through a Network Load Balancer, which was supported by two instances with PgBouncer, followed by all PostgreSQL servers.<br \/>\n<img decoding=\"async\" style=\"display: block; margin: 0 auto;\" src=\"\/wp-content\/uploads\/2019\/06\/8e5350d2466311593e11add30d5b1bdc.png\" alt=\"Fault-tolerant PostgreSQL cluster + Patroni. Implementation experience.\" \/><\/p>\n<p>To implement Patroni, we needed to choose a distributed configuration store for the cluster. Patroni works with distributed configuration storage systems such as etcd, Zookeeper, and Consul. We already have a fully operational Consul cluster in production that works in conjunction with Vault, and we haven't utilized it much otherwise. This is a great reason to start using Consul for its intended purpose.<\/p>\n<h3>How Patroni Works with Consul<\/h3>\n<p>We have a Consul cluster consisting of three nodes and a Patroni cluster made up of a leader and a replica (in Patroni, the master is called the cluster leader, and the slaves are replicas). Each instance of the Patroni cluster constantly sends information about the cluster's state to Consul. Therefore, it's always possible to discover the current configuration of the Patroni cluster and who the leader is at any given time from Consul.<\/p>\n<p><img decoding=\"async\" style=\"display: block; margin: 0 auto;\" src=\"\/wp-content\/uploads\/2019\/06\/3234594b363904424fdce64c9d77fca5.png\" alt=\"Fault-tolerant PostgreSQL cluster + Patroni. Implementation experience.\" \/><\/p>\n<p>To connect Patroni to Consul, it is enough to study the official documentation, which specifies that we need to provide a host in either http or https format, depending on how we are interacting with Consul, along with an optional connection scheme:<\/p>\n<pre><code class=\"plaintext\">host: the host:port for the Consul endpoint, in the format: http(s):\/\/host:port\nscheme: (optional) http or https, defaults to http<\/code><\/pre>\n<p>It seems simple, but this is where the pitfalls begin. We are working with Consul over a secured connection using https, and our connection configuration will look as follows:<\/p>\n<pre><code class=\"python\">consul:\n  host: https:\/\/server.production.consul:8080 \n  verify: true\n  cacert: {{ consul_cacert }}\n  cert: {{ consul_cert }}\n  key: {{ consul_key }}<\/code><\/pre>\n<p>But it doesn't work like that. When starting, Patroni cannot connect to Consul because it still tries to go through http.<\/p>\n<p>Understanding the issue was aided by the Patroni source code. Fortunately, it is written in Python. It turns out that the host parameter is not parsed at all, and the protocol needs to be specified in the scheme. This is what a working configuration block for using Consul looks like for us:<\/p>\n<pre><code class=\"python\">consul:\n  host: server.production.consul:8080\n  scheme: https\n  verify: true\n  cacert: {{ consul_cacert }}\n  cert: {{ consul_cert }}\n  key: {{ consul_key }}<\/code><\/pre>\n<h3>Consul-template<\/h3>\n<p>So, we've chosen the storage for the configuration. Now we need to understand how PgBouncer will switch its configuration when the leader changes in the Patroni cluster. The documentation doesn't provide an answer to this question, as it does not describe how to work with PgBouncer at all.<\/p>\n<p>In search of a solution, we found an article (unfortunately, I don't remember the title) that mentioned how Consul-template was very helpful in linking PgBouncer and Patroni. This prompted us to explore the workings of Consul-template.<\/p>\n<p>It turned out that Consul-template constantly monitors the PostgreSQL cluster configuration in Consul. When a leader changes, it updates the PgBouncer configuration and sends a command to reload it.<\/p>\n<p><img decoding=\"async\" style=\"display: block; margin: 0 auto;\" src=\"\/wp-content\/uploads\/2019\/06\/6cf92996a127bb6637ab81dc45fdd60a.png\" alt=\"Fault-tolerant PostgreSQL cluster + Patroni. Implementation experience.\" \/><\/p>\n<p>A big advantage of the template is that it is stored as code, so when adding a new shard, it's enough to make a new commit and automatically update the template, adhering to the principle of Infrastructure as Code.<\/p>\n<h3>New architecture with Patroni<\/h3>\n<p>As a result, we got the following operational scheme:<br \/>\n<img decoding=\"async\" style=\"display: block; margin: 0 auto;\" src=\"\/wp-content\/uploads\/2019\/06\/bf9a9eff675a26039e3f76b16c6cd713.png\" alt=\"Fault-tolerant PostgreSQL cluster + Patroni. Implementation experience.\" \/><\/p>\n<p>All application servers connect to the load balancer \u2192 behind it are two PgBouncer instances \u2192 each instance runs Consul-template, which monitors the status of each Patroni cluster and ensures the PgBouncer config is up to date, directing requests to the current leader of each cluster.<\/p>\n<h3>Manual testing<\/h3>\n<p>Before deploying this scheme to production, we ran it in a small test environment and checked the automatic switching functionality. We opened the board, moved a sticker, and at that moment, we 'killed' the leader of the cluster. In AWS, it's enough to stop the instance via the console.<\/p>\n<p><img decoding=\"async\" style=\"display: block; margin: 0 auto;\" src=\"\/wp-content\/uploads\/2019\/06\/d670fe373b774f1b52bfd8a8c3b53a21.png\" alt=\"Fault-tolerant PostgreSQL cluster + Patroni. Implementation experience.\" \/><\/p>\n<p>The sticker returned after 10-20 seconds and then began to move normally again. This means that the Patroni cluster worked correctly: it changed the leader, sent the information to Consul, and Consul-template immediately picked up this information, replaced the PgBouncer configuration, and sent a reload command.<\/p>\n<h2>How to survive under high load and maintain minimal downtime?<\/h2>\n<p>Everything works great! But new questions arise: How will it perform under high load? How to roll everything out to production quickly and safely?<\/p>\n<p>Answering the first question is aided by a test environment where we conduct load testing. It is completely identical to production in architecture and has generated test data, which is roughly equal in volume to production. We aim to simply 'kill' one of the PostgreSQL masters during the test and observe what happens. However, it is important to first check the automatic rollout, since we have several PostgreSQL shards in this environment, which allows us to thoroughly test the configuration scripts before production.<\/p>\n<p>Both tasks seem ambitious, but we are using PostgreSQL 9.6. Should we upgrade directly to 11.2?<\/p>\n<p>We decided to do this in two stages: first to upgrade to 11.2, then to run Patroni.<\/p>\n<h3>PostgreSQL Upgrade<\/h3>\n<p>To quickly upgrade the PostgreSQL version, you need to use the option <b>-k<\/b>, which creates hard links on disk and eliminates the need to copy your data. For databases of 300-400 GB, the upgrade takes just 1 second.<\/p>\n<p>We have many shards, so the upgrade needs to be done automatically. To achieve this, we wrote an Ansible playbook that executes the entire upgrade process for us:<\/p>\n<pre><code class=\"plaintext\">\/usr\/lib\/postgresql\/11\/bin\/pg_upgrade \n&lt;b&gt;--link &lt;\/b&gt;\n--old-datadir=&#039;&#039; --new-datadir=&#039;&#039; \n --old-bindir=&#039;&#039;  --new-bindir=&#039;&#039; \n --old-options=&#039; -c config_file=&#039; \n --new-options=&#039; -c config_file=&#039;<\/code><\/pre>\n<p>It is important to note that before starting the upgrade, you need to execute it with the parameter <b>\u2014check<\/b>, to ensure the upgrade is feasible. Our script also replaces configuration files during the upgrade. Our script completed in 30 seconds, which is an excellent result.<\/p>\n<h3>Starting Patroni<\/h3>\n<p>To solve the second problem, we just need to look at the Patroni configuration. There\u2019s an example configuration with initdb in the official repository, which is responsible for initializing a new database on the first run of Patroni. However, since we already have a prepared database, we simply removed that section from the configuration.<\/p>\n<p>When we started installing Patroni on the existing PostgreSQL cluster and running it, we encountered a new problem: both servers started as leaders. Patroni has no knowledge of the cluster's previous state and tries to start both servers as two separate clusters with the same name. To resolve this issue, you need to delete the data directory on the slave:<\/p>\n<pre><code class=\"plaintext\">rm -rf \/var\/lib\/postgresql\/<\/code><\/pre>\n<p><b>This must only be done on the slave!<\/b><\/p>\n<p>When connecting a clean replica, Patroni takes a base backup from the leader and restores it on the replica, then updates the state through the wal logs.<\/p>\n<p>Another challenge we faced is that all PostgreSQL clusters are named 'main' by default. While it's fine when each cluster is unaware of the others, when you want to use Patroni, all clusters must have unique names. The solution is to change the cluster name in the PostgreSQL configuration.<\/p>\n<h3>Load test<\/h3>\n<p>We conducted a test simulating user activity on the boards. When the load reached our average daily value, we repeated the same test, shutting down one instance with the leader PostgreSQL. The automatic failover functioned as expected: Patroni switched leaders, Consul-template updated the PgBouncer configuration, and issued a reload command. Our Grafana graphs showed that there were delays of 20-30 seconds and a small number of connection-related errors from the servers. This is a normal situation; such values are acceptable for our failover and certainly better than service downtime.<\/p>\n<h2>Patroni output on production<\/h2>\n<p>As a result, we ended up with the following plan:<\/p>\n<ul>\n<li>Deploy Consul-template on PgBouncer servers and launch;<\/li>\n<li>Upgrade PostgreSQL to version 11.2;<\/li>\n<li>Change the cluster name;<\/li>\n<li>Launch the Patroni cluster.<\/li>\n<\/ul>\n<p>Our scheme allows us to do the first point at almost any time; we can sequentially take each PgBouncer out of operation and deploy and launch consul-template on it. That's exactly what we did.<\/p>\n<p>For a quick rollout, we used Ansible since we had already tested all playbooks in the testing environment, and the total execution time for the full scenario was between 1.5 to 2 minutes for each shard. We could roll out to each shard sequentially without stopping our service, but we would need to shut down each PostgreSQL for a few minutes. In this case, users with data on that shard wouldn't be able to work fully during that time, which is unacceptable for us.<\/p>\n<p>The solution to this situation was a planned maintenance that takes place every 3 months. This window is for scheduled work when we completely shut down our service and update the database instances. One week remained until the next window, and we decided to simply wait and prepare further. During the waiting period, we took additional precautions: for each PostgreSQL shard, we launched a backup replica in case of failure to preserve the most recent data, and added a new instance for each shard to become a new replica in the Patroni cluster, so we wouldn't have to execute the command to delete data. All of this helped minimize the risk of errors.<br \/>\n<img decoding=\"async\" style=\"display: block; margin: 0 auto;\" src=\"\/wp-content\/uploads\/2019\/06\/29e5c5f50dbfebfe5665fa0aeb009728.png\" alt=\"Fault-tolerant PostgreSQL cluster + Patroni. Implementation experience.\" \/><\/p>\n<p>We restarted our service, everything worked as expected, and users continued to operate, but we noticed abnormally high load on the Consul servers on the graphs.<br \/>\n<img decoding=\"async\" style=\"display: block; margin: 0 auto;\" src=\"\/wp-content\/uploads\/2019\/06\/af0eef9ac235706c0aac62d2e1ee179d.png\" alt=\"Fault-tolerant PostgreSQL cluster + Patroni. Implementation experience.\" \/><\/p>\n<p>Why didn't we see this in the test environment? This issue illustrates the need to adhere to the principle of Infrastructure as Code and to refine the entire infrastructure, starting from test environments and ending with production. Otherwise, it's very easy to encounter a problem like the one we experienced. What happened? Consul first appeared in production, and then in test environments, resulting in the versions of Consul in the test environments being higher than in production. In one of the releases, a CPU leak issue when working with consul-template was fixed. Therefore, we simply updated Consul to resolve the issue.<\/p>\n<h3>Restart Patroni cluster<\/h3>\n<p>However, we encountered a new problem that we were unaware of. When updating Consul, we simply remove the Consul node from the cluster using the consul leave command \u2192 Patroni connects to another Consul server \u2192 everything works. But when we reached the last instance of the Consul cluster and sent it the consul leave command, all Patroni clusters restarted, and we saw the following error in the logs:<\/p>\n<pre><code class=\"plaintext\">ERROR: get_cluster\nTraceback (most recent call last):\n...\nRetryFailedError: &#039;Exceeded retry deadline&#039;\nERROR: Error communicating with DCS\n&lt;b&gt;LOG: database system is shut down&lt;\/b&gt;<\/code><\/pre>\n<p>The Patroni cluster was unable to obtain information about its cluster and restarted.<\/p>\n<p>To find a solution, we contacted the authors of Patroni through an issue on GitHub. They suggested improvements to our configuration files:<\/p>\n<pre><code class=\"python\">consul:\n consul.checks: []\nbootstrap:\n dcs:\n   retry_timeout: 8<\/code><\/pre>\n<p>We were able to reproduce the problem in the test environment and tested these parameters there, but unfortunately, they did not work.<\/p>\n<p>The issue remains unresolved. We plan to try the following solutions:<\/p>\n<ul>\n<li>Use a Consul agent on each instance of the Patroni cluster;<\/li>\n<li>Fix the problem in the code.<\/li>\n<\/ul>\n<p>We understand where the error occurs: likely, the problem lies in the use of the default timeout, which is not overridden by the configuration file. When the last Consul server is removed from the cluster, the entire Consul cluster hangs for longer than a second, preventing Patroni from obtaining the cluster state and causing it to completely restart the entire cluster.<\/p>\n<p>Fortunately, we haven't encountered any other errors.<\/p>\n<h2>Summary of using Patroni<\/h2>\n<p>After successfully launching Patroni, we added an additional replica in each cluster. Now, each cluster has a semblance of quorum: one leader and two replicas, to guard against split-brain during failover.<br \/>\n<img decoding=\"async\" style=\"display: block; margin: 0 auto;\" src=\"\/wp-content\/uploads\/2019\/06\/3bb4c0495fea274b04edd2e99b59eacb.png\" alt=\"Fault-tolerant PostgreSQL cluster + Patroni. Implementation experience.\" \/><\/p>\n<p>In production, Patroni has been running for over three months. During this time, it has already proved to be invaluable. Recently, the leader of one of the clusters failed in AWS, and the automatic failover worked, allowing users to continue working. Patroni fulfilled its main task.<\/p>\n<p><b>A brief summary of using Patroni:<\/b><\/p>\n<ul>\n<li>Ease of configuration changes. It's enough to change the configuration on one instance, and it will pull through to the entire cluster. If a restart is needed to apply the new configuration, Patroni will notify about it. Patroni can restart the entire cluster with a single command, which is also very convenient.<\/li>\n<li>Automatic failover works and has already proven invaluable.<\/li>\n<li>Upgrading PostgreSQL without application downtime. It is necessary to first upgrade the replicas to the new version, then change the leader in the Patroni cluster and upgrade the old leader. During this process, necessary testing of the automatic failover occurs.<\/li>\n<\/ul>\n<p>Source: <a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/miro\/blog\/457326\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443, \u043a\u0430\u043a \u043c\u044b \u043f\u043e\u0434\u043e\u0448\u043b\u0438 \u043a \u0432\u043e\u043f\u0440\u043e\u0441\u0443 \u043e\u0442\u043a\u0430\u0437\u043e\u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u043e\u0441\u0442\u0438 PostgreSQL, \u043f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u0441\u0442\u0430\u043b\u043e \u0434\u043b\u044f \u043d\u0430\u0441 \u0432\u0430\u0436\u043d\u043e \u0438 \u0447\u0442\u043e \u0432 \u0438\u0442\u043e\u0433\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c. \u0423 \u043d\u0430\u0441 \u0432\u044b\u0441\u043e\u043a\u043e\u043d\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441: 2,5 \u043c\u043b\u043d \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u043f\u043e \u0432\u0441\u0435\u043c\u0443 \u043c\u0438\u0440\u0443, 50\u041a+ \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u043a\u0430\u0436\u0434\u044b\u0439 \u0434\u0435\u043d\u044c. \u0421\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0432 Amazone \u0432 \u043e\u0434\u043d\u043e\u043c \u0440\u0435\u0433\u0438\u043e\u043d\u0435 \u0418\u0440\u043b\u0430\u043d\u0434\u0438\u0438: \u0432 \u0440\u0430\u0431\u043e\u0442\u0435 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e 100+ \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432, \u0438\u0437 \u043d\u0438\u0445 \u043f\u043e\u0447\u0442\u0438 50 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":26749,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-35706","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443, \u043a\u0430\u043a \u043c\u044b \u043f\u043e\u0434\u043e\u0448\u043b\u0438 \u043a \u0432\u043e\u043f\u0440\u043e\u0441\u0443 \u043e\u0442\u043a\u0430\u0437\u043e\u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u043e\u0441\u0442\u0438 PostgreSQL, \u043f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u0441\u0442\u0430\u043b\u043e \u0434\u043b\u044f \u043d\u0430\u0441 \u0432\u0430\u0436\u043d\u043e \u0438 \u0447\u0442\u043e \u0432 \u0438\u0442\u043e\u0433\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/otkazoustojchivyj-klaster-postgresql-patroni-opyt-vnedreniya\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u041e\u0442\u043a\u0430\u0437\u043e\u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0442\u0435\u0440 PostgreSQL + Patroni. \u041e\u043f\u044b\u0442 \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u044f | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443, \u043a\u0430\u043a \u043c\u044b \u043f\u043e\u0434\u043e\u0448\u043b\u0438 \u043a \u0432\u043e\u043f\u0440\u043e\u0441\u0443 \u043e\u0442\u043a\u0430\u0437\u043e\u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u043e\u0441\u0442\u0438 PostgreSQL, \u043f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u0441\u0442\u0430\u043b\u043e \u0434\u043b\u044f \u043d\u0430\u0441 \u0432\u0430\u0436\u043d\u043e \u0438 \u0447\u0442\u043e \u0432 \u0438\u0442\u043e\u0433\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/otkazoustojchivyj-klaster-postgresql-patroni-opyt-vnedreniya\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T19:05:50+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2026-05-18T18:58:46+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Fault-tolerant PostgreSQL cluster + Patroni. Implementation experience | ProHoster","description":"In this article, I will explain how we approached the issue of PostgreSQL fault tolerance, why it became important to us, and what the outcome was.","canonical_url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/otkazoustojchivyj-klaster-postgresql-patroni-opyt-vnedreniya","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"en_US","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u041e\u0442\u043a\u0430\u0437\u043e\u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0442\u0435\u0440 PostgreSQL + Patroni. \u041e\u043f\u044b\u0442 \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u044f | ProHoster","og:description":"\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443, \u043a\u0430\u043a \u043c\u044b \u043f\u043e\u0434\u043e\u0448\u043b\u0438 \u043a \u0432\u043e\u043f\u0440\u043e\u0441\u0443 \u043e\u0442\u043a\u0430\u0437\u043e\u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u043e\u0441\u0442\u0438 PostgreSQL, \u043f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u0441\u0442\u0430\u043b\u043e \u0434\u043b\u044f \u043d\u0430\u0441 \u0432\u0430\u0436\u043d\u043e \u0438 \u0447\u0442\u043e \u0432 \u0438\u0442\u043e\u0433\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c.","og:url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/otkazoustojchivyj-klaster-postgresql-patroni-opyt-vnedreniya","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T19:05:50+00:00","article:modified_time":"2026-05-18T18:58:46+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"35706","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-22 00:26:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 13:51:06","updated":"2026-01-22 00:26:19","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/35706","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/comments?post=35706"}],"version-history":[{"count":1,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/35706\/revisions"}],"predecessor-version":[{"id":172654,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/35706\/revisions\/172654"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media\/26749"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media?parent=35706"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/categories?post=35706"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/tags?post=35706"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}