<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Diego Gil Serrano</title>
    <description>The latest articles on DEV Community by Diego Gil Serrano (@siemcompliance).</description>
    <link>https://dev.to/siemcompliance</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4205928%2F14181f41-1b07-446c-bdb8-9bb36b3663df.png</url>
      <title>DEV Community: Diego Gil Serrano</title>
      <link>https://dev.to/siemcompliance</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/siemcompliance"/>
    <language>en</language>
    <item>
      <title>What NIS2 Article 21 actually asks you to log, and how to do it with a self-hosted SIEM</title>
      <dc:creator>Diego Gil Serrano</dc:creator>
      <pubDate>Sun, 25 Oct 2026 05:51:39 +0000</pubDate>
      <link>https://dev.to/siemcompliance/what-nis2-article-21-actually-asks-you-to-log-and-how-to-do-it-with-a-self-hosted-siem-b0b</link>
      <guid>https://dev.to/siemcompliance/what-nis2-article-21-actually-asks-you-to-log-and-how-to-do-it-with-a-self-hosted-siem-b0b</guid>
      <description>&lt;p&gt;If you run infrastructure for a company that falls under NIS2 (or sells to one), you will eventually be asked: "Are we logging what NIS2 requires?" The Directive itself does not contain the word SIEM. But read Articles 21 and 23 together with the Commission's Implementing Regulation (EU) 2024/2690 and the logging requirements become quite specific.&lt;/p&gt;

&lt;p&gt;This post maps those texts to concrete log sources and events, and shows how to cover them with tools you host yourself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a practical summary, not legal advice. Always check the official text and your national transposition.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Where logging appears in the Directive
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Article 21(1)&lt;/strong&gt; requires essential and important entities to take "appropriate and proportionate technical, operational and organisational measures" to manage risks to their network and information systems. &lt;strong&gt;Article 21(2)&lt;/strong&gt; lists the minimum set. Three items depend directly on logs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;(b) incident handling.&lt;/strong&gt; You cannot detect, analyse or contain an incident without a record of what happened.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(f) policies and procedures to assess the effectiveness of cybersecurity risk-management measures.&lt;/strong&gt; Logs are how you prove a control works, not just that it exists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(j) the use of multi-factor authentication or continuous authentication solutions.&lt;/strong&gt; Authentication events are the evidence that MFA is actually enforced.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Article 23&lt;/strong&gt; adds the clock: an &lt;strong&gt;early warning within 24 hours&lt;/strong&gt; of becoming aware of a significant incident, an &lt;strong&gt;incident notification within 72 hours&lt;/strong&gt;, and a &lt;strong&gt;final report within one month&lt;/strong&gt; of the notification. Producing a reliable timeline in 24 hours is very hard if logs are scattered over dozens of machines with drifting clocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it gets concrete: Implementing Regulation 2024/2690
&lt;/h2&gt;

&lt;p&gt;The Implementing Regulation details the Article 21 measures for a specific set of digital providers: DNS service providers and TLD registries, cloud computing, data centres, content delivery networks, managed service providers, managed security service providers, online marketplaces, search engines, social networks and trust service providers. For everyone else it is not binding, but it is the most precise public description of what "incident handling" means in practice, and auditors use it as a reference.&lt;/p&gt;

&lt;p&gt;Point 3.2 of its annex ("monitoring and logging") asks entities to keep logs that include, where appropriate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;relevant outbound and inbound network traffic;&lt;/li&gt;
&lt;li&gt;creation, modification or deletion of users and extension of permissions;&lt;/li&gt;
&lt;li&gt;access to systems and applications;&lt;/li&gt;
&lt;li&gt;authentication-related events;&lt;/li&gt;
&lt;li&gt;all privileged access and activities performed by administrative accounts;&lt;/li&gt;
&lt;li&gt;access or changes to critical configuration and backup files;&lt;/li&gt;
&lt;li&gt;event logs and logs from security tools such as antivirus, IDS or firewalls;&lt;/li&gt;
&lt;li&gt;use of system resources and their performance;&lt;/li&gt;
&lt;li&gt;physical access to facilities;&lt;/li&gt;
&lt;li&gt;access to and use of network equipment and devices;&lt;/li&gt;
&lt;li&gt;activation, stopping and pausing of the various logs;&lt;/li&gt;
&lt;li&gt;environmental events.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It also asks for regular review of logs, automated alarms where appropriate, a defined retention period, protection against unauthorised access or modification, synchronised clocks, and monitoring that is reliable and does not depend on the systems it watches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mapping requirements to real sources
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Typical source&lt;/th&gt;
&lt;th&gt;What to collect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Network traffic&lt;/td&gt;
&lt;td&gt;Firewall, VPN concentrator, reverse proxy / WAF&lt;/td&gt;
&lt;td&gt;Allowed and denied connections, VPN sessions, admin logins to the firewall&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User lifecycle and permissions&lt;/td&gt;
&lt;td&gt;Active Directory / LDAP, Linux &lt;code&gt;/etc/passwd&lt;/code&gt; changes&lt;/td&gt;
&lt;td&gt;Windows events 4720 (user created), 4726 (user deleted), 4728 / 4732 / 4756 (member added to a security group)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authentication&lt;/td&gt;
&lt;td&gt;Domain controllers, Linux &lt;code&gt;auth.log&lt;/code&gt; / &lt;code&gt;secure&lt;/code&gt;, SSH, web apps&lt;/td&gt;
&lt;td&gt;4624 (logon), 4625 (failed logon), 4740 (account locked out), &lt;code&gt;sshd&lt;/code&gt; accepted/failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privileged activity&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sudo&lt;/code&gt;, Windows admin groups, hypervisor (ESXi) logs&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sudo&lt;/code&gt; commands, admin logins to vCenter/ESXi hosts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Critical config and backups&lt;/td&gt;
&lt;td&gt;Backup server, config management, storage arrays&lt;/td&gt;
&lt;td&gt;Job results, deleted snapshots, config changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security tools&lt;/td&gt;
&lt;td&gt;Antivirus/EDR, IDS, firewall&lt;/td&gt;
&lt;td&gt;Detections and policy changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logging stopped&lt;/td&gt;
&lt;td&gt;Windows, rsyslog, agents&lt;/td&gt;
&lt;td&gt;Event 1102 (audit log cleared); "source silent for N minutes" alerts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is the one most setups miss. Requirement 11 is easiest to meet with an alert that fires when a source &lt;strong&gt;stops&lt;/strong&gt; sending.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal self-hosted pipeline
&lt;/h2&gt;

&lt;p&gt;You do not need a large platform to start. The pieces:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Time.&lt;/strong&gt; Same NTP source everywhere, and check drift:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chronyc tracking | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'System time|Leap status'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Forwarding from Linux.&lt;/strong&gt; With rsyslog, forward everything over TCP to your collector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/rsyslog.d/90-forward.conf
&lt;/span&gt;*.* @@&lt;span class="n"&gt;logs&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;internal&lt;/span&gt;:&lt;span class="m"&gt;514&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use TLS (&lt;code&gt;omfwd&lt;/code&gt; with &lt;code&gt;StreamDriver="gtls"&lt;/code&gt;) if logs cross untrusted networks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Windows.&lt;/strong&gt; Enable the advanced audit policy subcategories for logon, account management and security group management, and ship the Security log with an agent or Windows Event Forwarding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Network devices and hypervisors.&lt;/strong&gt; Firewalls, switches and ESXi hosts all speak syslog; point them at the collector.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Detection rules you can explain to an auditor.&lt;/strong&gt; Start with a handful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Brute force:&lt;/strong&gt; more than N failed logons for one account from one source in M minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password spraying:&lt;/strong&gt; one source failing against many different accounts in a short window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lockouts:&lt;/strong&gt; any 4740 on a privileged account.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Out-of-hours admin logins.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changes to admin groups&lt;/strong&gt; (4728/4732/4756 on Domain Admins and equivalents).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source silent:&lt;/strong&gt; no events from a host for longer than its normal interval.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;6. Review and tickets.&lt;/strong&gt; Every alert should land somewhere with an owner, a status and a history. "We review logs" is a claim; a list of reviewed alerts with dates and names is evidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Retention and integrity.&lt;/strong&gt; Neither the Directive nor the Regulation fixes a number of days. They ask for a defined period, justified by your risk assessment and other legal obligations. Document it, enforce it, and restrict who can read and delete logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why self-hosting matters here
&lt;/h2&gt;

&lt;p&gt;Logs contain usernames, IP addresses, hostnames and sometimes personal data. Shipping all of that to a third-party cloud brings its own contractual, data-protection and supply-chain questions (supply chain security is itself Article 21(2)(d)). Many public-sector and regulated organisations prefer, or are required, to keep the data on their own infrastructure. A self-hosted collector avoids that discussion entirely, at the cost of operating it yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] List of log sources in scope, and an alert when any of them goes silent&lt;/li&gt;
&lt;li&gt;[ ] Authentication, user lifecycle and admin-group events collected from every domain controller&lt;/li&gt;
&lt;li&gt;[ ] Firewall and VPN logs centralised&lt;/li&gt;
&lt;li&gt;[ ] Common NTP source and drift check&lt;/li&gt;
&lt;li&gt;[ ] Detection rules for brute force, spraying, lockouts, out-of-hours admin activity&lt;/li&gt;
&lt;li&gt;[ ] Alerts tracked as tickets with owner and history&lt;/li&gt;
&lt;li&gt;[ ] Documented retention period and restricted access to logs&lt;/li&gt;
&lt;li&gt;[ ] A tested way to produce an incident timeline within 24 hours&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I work on SIEM Compliance, a self-hosted SIEM from NUTRADELIS S.L. (Spain). Its Community edition is free with no limit on servers, agents or log volume, collects logs from firewalls, Windows and Linux servers, ESXi, switches and Cloudflare, and ships with rules like the ones above. Paid plans add an audit module with NIS2 and ENS evidence packs. The &lt;a href="https://www.siemcompliance.com/recursos/nis2-registro-monitorizacion-eventos/?lang=en" rel="noopener noreferrer"&gt;original guide&lt;/a&gt; covers the same topic with a Spain-specific section.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Directive (EU) 2022/2555 (NIS2), Articles 21 and 23, EUR-Lex: &lt;a href="https://eur-lex.europa.eu/eli/dir/2022/2555/oj" rel="noopener noreferrer"&gt;https://eur-lex.europa.eu/eli/dir/2022/2555/oj&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Commission Implementing Regulation (EU) 2024/2690, Annex, point 3.2, EUR-Lex: &lt;a href="https://eur-lex.europa.eu/eli/reg_impl/2024/2690/oj" rel="noopener noreferrer"&gt;https://eur-lex.europa.eu/eli/reg_impl/2024/2690/oj&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Article 21 text (secondary reproduction used for cross-checking): &lt;a href="https://luxgap.com/lois/nis2/art-21/?lang=en" rel="noopener noreferrer"&gt;https://luxgap.com/lois/nis2/art-21/?lang=en&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Annex point 3.2.3 list of logged events (secondary reproduction used for cross-checking): &lt;a href="https://www.cyberday.ai/requirement/nis2-guide-3-2-3-log-management-and-review" rel="noopener noreferrer"&gt;https://www.cyberday.ai/requirement/nis2-guide-3-2-3-log-management-and-review&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Microsoft, Windows security audit events reference: &lt;a href="https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/security-auditing-overview" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/security-auditing-overview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SIEM Compliance, original guide: &lt;a href="https://www.siemcompliance.com/recursos/nis2-registro-monitorizacion-eventos/?lang=en" rel="noopener noreferrer"&gt;https://www.siemcompliance.com/recursos/nis2-registro-monitorizacion-eventos/?lang=en&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>sysadmin</category>
      <category>linux</category>
    </item>
    <item>
      <title>Why Patroni failover takes 26–49 seconds with default settings (and how to make it faster)</title>
      <dc:creator>Diego Gil Serrano</dc:creator>
      <pubDate>Sun, 25 Oct 2026 05:41:37 +0000</pubDate>
      <link>https://dev.to/siemcompliance/why-patroni-failover-takes-26-49-seconds-with-default-settings-and-how-to-make-it-faster-h7n</link>
      <guid>https://dev.to/siemcompliance/why-patroni-failover-takes-26-49-seconds-with-default-settings-and-how-to-make-it-faster-h7n</guid>
      <description>&lt;p&gt;You build a Patroni + etcd + HAProxy cluster, kill the primary in a test, and your write loop stops for half a minute or more. Nothing is broken. That is what the default settings are designed to do. This post explains where those seconds go and what you can change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three settings that matter
&lt;/h2&gt;

&lt;p&gt;All three live in Patroni's dynamic configuration (stored in the DCS, changed with &lt;code&gt;patronictl edit-config&lt;/code&gt;). From the official documentation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Minimum&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;loop_wait&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"the number of seconds the loop will sleep" between HA cycles&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ttl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the TTL of the leader lock; "think of it as the length of time before initiation of the automatic failover process"&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;retry_timeout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"timeout for DCS and PostgreSQL operation retries"; "DCS or network issues shorter than this will not cause Patroni to demote the leader"&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The documentation also sets a hard rule when you change them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loop_wait + 2 * retry_timeout &amp;lt;= ttl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With defaults: 10 + 2 × 10 = 30 ≤ 30. The defaults sit exactly on the limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when the primary dies
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;While healthy, the primary renews the &lt;strong&gt;leader key&lt;/strong&gt; in etcd on every HA loop, roughly every &lt;code&gt;loop_wait&lt;/code&gt; (10 s).&lt;/li&gt;
&lt;li&gt;The primary crashes (power loss, kernel panic, hard network isolation). Nobody renews the key.&lt;/li&gt;
&lt;li&gt;The key lives until &lt;code&gt;ttl&lt;/code&gt; (30 s) after its &lt;strong&gt;last renewal&lt;/strong&gt;. Since renewals happen every ~10 s, the failure can land anywhere in that cycle: the key expires between &lt;strong&gt;~20 s and ~30 s&lt;/strong&gt; after the failure.&lt;/li&gt;
&lt;li&gt;A replica notices the key is gone on its next HA cycle (between 0 and &lt;code&gt;loop_wait&lt;/code&gt; seconds later), checks it is healthy enough (for example within &lt;code&gt;maximum_lag_on_failover&lt;/code&gt;), takes the key and promotes PostgreSQL.&lt;/li&gt;
&lt;li&gt;HAProxy, which polls each node's &lt;code&gt;GET /primary&lt;/code&gt; (HTTP 200 only on the node "running as the primary with leader lock"), needs one or more successful checks on the new primary before it routes writes there. With checks every 2 s and a &lt;code&gt;rise&lt;/code&gt; greater than 1, that adds a few more seconds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Add up the pieces: 20–30 s for the key to expire, 0–10 s for a replica to notice, a second or two to promote, and a few seconds of health checks. You get the &lt;strong&gt;26–49 s&lt;/strong&gt; range you see with plain Patroni + etcd defaults and HAProxy checking every 2 s. The exact figure depends on where in the renewal cycle the failure happens, on how fast promotion completes, and on your health check settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two important exceptions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clean shutdown is fast.&lt;/strong&gt; If you stop Patroni normally (&lt;code&gt;systemctl stop patroni&lt;/code&gt;, a planned reboot) it releases the leader key on the way out, so a replica can take over without waiting for the TTL. That is why "I rebooted the primary and failover took 5 seconds" and "I pulled the power cable and it took 40" are both true.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A frozen primary is the slow case.&lt;/strong&gt; A node that hangs (stuck I/O, process stopped) may keep its key until it expires, and it is still in HAProxy's backend until checks fail. This is the scenario to test, because it is the one that hurts most.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to make it faster
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Shrink &lt;code&gt;ttl&lt;/code&gt;, &lt;code&gt;loop_wait&lt;/code&gt; and &lt;code&gt;retry_timeout&lt;/code&gt; together
&lt;/h3&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# patronictl -c /etc/patroni/patroni.yml edit-config&lt;/span&gt;
&lt;span class="na"&gt;loop_wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="na"&gt;retry_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5 + 2 × 5 = 15 ≤ 20, so the rule holds, and the worst-case key expiry drops from ~30 s to ~20 s after the last renewal. &lt;code&gt;ttl&lt;/code&gt; cannot go below 20.&lt;/p&gt;

&lt;p&gt;The trade-off is real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A shorter &lt;code&gt;retry_timeout&lt;/code&gt; means a shorter etcd or network hiccup is enough to &lt;strong&gt;demote a healthy primary&lt;/strong&gt;. If your etcd runs on slow disks or across a flaky link, you will trade slow failovers for unnecessary ones.&lt;/li&gt;
&lt;li&gt;A shorter &lt;code&gt;loop_wait&lt;/code&gt; means more frequent DCS writes and more work per node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Measure your etcd latency (&lt;code&gt;etcdctl check perf&lt;/code&gt;, fsync metrics) before going aggressive.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Tune the HAProxy health checks
&lt;/h3&gt;

&lt;p&gt;The usual Patroni HAProxy backend looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;backend&lt;/span&gt; &lt;span class="s"&gt;primary&lt;/span&gt;
    &lt;span class="s"&gt;option&lt;/span&gt; &lt;span class="s"&gt;httpchk&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt; &lt;span class="n"&gt;/primary&lt;/span&gt;
    &lt;span class="s"&gt;http-check&lt;/span&gt; &lt;span class="s"&gt;expect&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="s"&gt;default-server&lt;/span&gt; &lt;span class="s"&gt;inter&lt;/span&gt; &lt;span class="s"&gt;2s&lt;/span&gt; &lt;span class="s"&gt;fall&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="s"&gt;rise&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="s"&gt;-marked-down&lt;/span&gt; &lt;span class="s"&gt;shutdown-sessions&lt;/span&gt;
    &lt;span class="s"&gt;server&lt;/span&gt; &lt;span class="s"&gt;db1&lt;/span&gt; &lt;span class="nf"&gt;10.0.0.11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5432&lt;/span&gt; &lt;span class="s"&gt;check&lt;/span&gt; &lt;span class="s"&gt;port&lt;/span&gt; &lt;span class="mi"&gt;8008&lt;/span&gt;
    &lt;span class="s"&gt;server&lt;/span&gt; &lt;span class="s"&gt;db2&lt;/span&gt; &lt;span class="nf"&gt;10.0.0.12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5432&lt;/span&gt; &lt;span class="s"&gt;check&lt;/span&gt; &lt;span class="s"&gt;port&lt;/span&gt; &lt;span class="mi"&gt;8008&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;inter&lt;/code&gt; × &lt;code&gt;rise&lt;/code&gt; is the minimum time before the new primary receives traffic, and &lt;code&gt;inter&lt;/code&gt; × &lt;code&gt;fall&lt;/code&gt; the time before the old one is taken out. &lt;code&gt;on-marked-down shutdown-sessions&lt;/code&gt; closes existing connections to a node marked down, so clients do not keep writing into a dead socket. Lowering &lt;code&gt;inter&lt;/code&gt; to 1 s is cheap; it only adds HTTP requests to the Patroni REST API.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Make clients reconnect quickly
&lt;/h3&gt;

&lt;p&gt;Database failover time is not application recovery time. Check your driver's connect timeout, TCP keepalives and retry logic. A pool that waits 2 minutes on a half-open TCP connection will hide every second you saved in Patroni.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Handle the frozen primary explicitly
&lt;/h3&gt;

&lt;p&gt;Lower TTLs help, but a hung node is a detection problem, not just a timer problem. Options include Patroni's watchdog support (so a node that stops running its HA loop is reset) and external fencing that removes the old primary from every load balancer as soon as a failover is decided.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Measure, do not assume
&lt;/h3&gt;

&lt;p&gt;Run a write loop through the load balancer and look at the gap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;psql &lt;span class="nt"&gt;-h&lt;/span&gt; VIRTUAL_IP &lt;span class="nt"&gt;-p&lt;/span&gt; 5000 &lt;span class="nt"&gt;-U&lt;/span&gt; app &lt;span class="nt"&gt;-d&lt;/span&gt; app &lt;span class="nt"&gt;-qAt&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"INSERT INTO ha_test DEFAULT VALUES RETURNING now()"&lt;/span&gt; 2&amp;gt;&amp;amp;1
  &lt;span class="nb"&gt;sleep &lt;/span&gt;0.5
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then try a switchover, a clean stop, a &lt;code&gt;kill -9&lt;/code&gt;, and a &lt;code&gt;SIGSTOP&lt;/code&gt; of Patroni and PostgreSQL on the primary (test environments only). Each one exercises a different path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;With defaults, the leader key expires 20–30 s after a hard failure; add detection, promotion and health checks and you land in 26–49 s.&lt;/li&gt;
&lt;li&gt;Any change must respect &lt;code&gt;loop_wait + 2 * retry_timeout &amp;lt;= ttl&lt;/code&gt;, and &lt;code&gt;ttl&lt;/code&gt; cannot go below 20.&lt;/li&gt;
&lt;li&gt;Faster settings increase the risk of demoting a healthy primary on short DCS hiccups.&lt;/li&gt;
&lt;li&gt;HAProxy check intervals and client reconnect behaviour matter as much as Patroni's timers.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I work on HighAvailabilityData, a free on-premise console from NUTRADELIS S.L. for Patroni clusters. Instead of only waiting for the TTL, it lets the HAProxy instances and the console vote: if a strict majority sees the primary down for &lt;code&gt;lb_failover_after&lt;/code&gt; (10 s by default), it promotes the healthy replica with the most WAL applied through Patroni's &lt;code&gt;POST /failover&lt;/code&gt;, releases the stale leader key with a compare-and-delete and puts the old primary in maintenance on every HAProxy. In our tests writes came back in about 15–20 s; measure in your own environment. The &lt;a href="https://www.highavailabilitydata.com/resources/failover-postgresql?lang=en" rel="noopener noreferrer"&gt;original article&lt;/a&gt; includes a step-by-step test plan.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Patroni, Dynamic Configuration Settings (&lt;code&gt;loop_wait&lt;/code&gt;, &lt;code&gt;ttl&lt;/code&gt;, &lt;code&gt;retry_timeout&lt;/code&gt;, defaults, minimums and the &lt;code&gt;loop_wait + 2*retry_timeout &amp;lt;= ttl&lt;/code&gt; rule): &lt;a href="https://patroni.readthedocs.io/en/latest/dynamic_configuration.html" rel="noopener noreferrer"&gt;https://patroni.readthedocs.io/en/latest/dynamic_configuration.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Patroni, REST API (&lt;code&gt;GET /primary&lt;/code&gt;, &lt;code&gt;POST /failover&lt;/code&gt;): &lt;a href="https://patroni.readthedocs.io/en/latest/rest_api.html" rel="noopener noreferrer"&gt;https://patroni.readthedocs.io/en/latest/rest_api.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Patroni, DCS Failsafe Mode (primary demoted when the leader lock update fails): &lt;a href="https://patroni.readthedocs.io/en/latest/dcs_failsafe_mode.html" rel="noopener noreferrer"&gt;https://patroni.readthedocs.io/en/latest/dcs_failsafe_mode.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Patroni, Watchdog support: &lt;a href="https://patroni.readthedocs.io/en/latest/watchdog.html" rel="noopener noreferrer"&gt;https://patroni.readthedocs.io/en/latest/watchdog.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HAProxy configuration manual (&lt;code&gt;inter&lt;/code&gt;, &lt;code&gt;fall&lt;/code&gt;, &lt;code&gt;rise&lt;/code&gt;, &lt;code&gt;on-marked-down&lt;/code&gt;): &lt;a href="https://docs.haproxy.org/" rel="noopener noreferrer"&gt;https://docs.haproxy.org/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pigsty documentation, Patroni passive failure RTO model (best/worst case per phase): &lt;a href="https://pigsty.io/docs/concept/ha/failure/passive/" rel="noopener noreferrer"&gt;https://pigsty.io/docs/concept/ha/failure/passive/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HighAvailabilityData, original article: &lt;a href="https://www.highavailabilitydata.com/resources/failover-postgresql?lang=en" rel="noopener noreferrer"&gt;https://www.highavailabilitydata.com/resources/failover-postgresql?lang=en&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>devops</category>
      <category>sre</category>
    </item>
    <item>
      <title>PostgreSQL high availability with only 2 servers: where the third vote comes from</title>
      <dc:creator>Diego Gil Serrano</dc:creator>
      <pubDate>Sun, 25 Oct 2026 05:40:25 +0000</pubDate>
      <link>https://dev.to/siemcompliance/postgresql-high-availability-with-only-2-servers-where-the-third-vote-comes-from-35oj</link>
      <guid>https://dev.to/siemcompliance/postgresql-high-availability-with-only-2-servers-where-the-third-vote-comes-from-35oj</guid>
      <description>&lt;p&gt;Many teams have budget for &lt;strong&gt;two&lt;/strong&gt; database servers, not three. Almost every PostgreSQL high-availability guide, on the other hand, opens with "you need at least three nodes".&lt;/p&gt;

&lt;p&gt;Both statements are true at the same time. You can run automatic failover with two &lt;strong&gt;database&lt;/strong&gt; servers, but you cannot run it safely with two &lt;strong&gt;votes&lt;/strong&gt;. This post explains why, and where the third vote can come from.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question every failover has to answer
&lt;/h2&gt;

&lt;p&gt;When a replica stops hearing from the primary, it cannot tell the difference between two situations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The primary is down.&lt;/li&gt;
&lt;li&gt;The network between the two nodes is down, and the primary is happily serving clients.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the replica promotes itself in case 2, you have &lt;strong&gt;split-brain&lt;/strong&gt;: two primaries accepting different writes. When the network comes back, those two histories cannot be merged automatically, and somebody loses data.&lt;/p&gt;

&lt;p&gt;Consensus systems solve this with a &lt;strong&gt;majority&lt;/strong&gt; rule: an action only happens if more than half of the voters agree. With two voters the majority is two. If they disagree, nobody has a majority and nothing happens. That is safe, but it also means no failover.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Patroni and etcd use the majority
&lt;/h2&gt;

&lt;p&gt;Patroni manages PostgreSQL on each node and stores cluster state in a distributed configuration store (DCS), most often etcd:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The primary holds a &lt;strong&gt;leader key&lt;/strong&gt; in etcd and renews it on every HA loop, every &lt;code&gt;loop_wait&lt;/code&gt; seconds (default 10).&lt;/li&gt;
&lt;li&gt;The key expires after &lt;code&gt;ttl&lt;/code&gt; seconds (default 30). Only when it expires can a replica take it and promote itself.&lt;/li&gt;
&lt;li&gt;If the primary cannot update the leader key, Patroni demotes it. In the documentation's words: "In case the update of the leader lock fails, Postgres is immediately demoted and started as read-only." (Patroni's optional &lt;code&gt;failsafe_mode&lt;/code&gt; relaxes this when the primary can still reach every other Patroni member.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So Patroni itself never decides by local opinion: it delegates the "who is primary" decision to etcd. And etcd is a Raft cluster that needs a majority of its members to accept writes. The etcd FAQ gives the numbers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;etcd members&lt;/th&gt;
&lt;th&gt;Majority&lt;/th&gt;
&lt;th&gt;Failures tolerated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two etcd members tolerate &lt;strong&gt;zero&lt;/strong&gt; failures, exactly like one. If one etcd member is on each database server and one server dies, the survivor has 1 of 2 votes, etcd loses quorum, no leader key can be written, and the surviving node cannot become (or even stay) primary. The etcd FAQ also notes that adding a member to reach an even number "doesn't buy additional fault tolerance", which is why the usual recommendation is 3 or 5 members.&lt;/p&gt;

&lt;p&gt;The constraint is on &lt;strong&gt;voters&lt;/strong&gt;, not on PostgreSQL instances. That is the door to two-server designs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: a third database server
&lt;/h2&gt;

&lt;p&gt;Three nodes, each running PostgreSQL, Patroni and etcd. It is the simplest topology to reason about, and you get a second replica for reads or for synchronous replication. It is also the most expensive option, because the third machine needs the same disk and memory as the others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: a witness that only runs etcd
&lt;/h2&gt;

&lt;p&gt;A small VM that runs &lt;strong&gt;only etcd&lt;/strong&gt;, no PostgreSQL and no Patroni. etcd is light, so a modest machine is enough. What matters is placement: the witness must not share a failure domain with either database server (different hypervisor host at a minimum, a different room or site if you have one).&lt;/p&gt;

&lt;p&gt;A minimal sketch of the three-member etcd cluster (names and IPs are examples):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# On the witness (repeat on db1 and db2 with their own --name and IPs)&lt;/span&gt;
etcd &lt;span class="nt"&gt;--name&lt;/span&gt; witness &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--initial-advertise-peer-urls&lt;/span&gt; http://10.0.0.13:2380 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--listen-peer-urls&lt;/span&gt; http://10.0.0.13:2380 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--listen-client-urls&lt;/span&gt; http://10.0.0.13:2379,http://127.0.0.1:2379 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--advertise-client-urls&lt;/span&gt; http://10.0.0.13:2379 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--initial-cluster&lt;/span&gt; &lt;span class="nv"&gt;db1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://10.0.0.11:2380,db2&lt;span class="o"&gt;=&lt;/span&gt;http://10.0.0.12:2380,witness&lt;span class="o"&gt;=&lt;/span&gt;http://10.0.0.13:2380 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--initial-cluster-state&lt;/span&gt; new
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in each &lt;code&gt;patroni.yml&lt;/code&gt;, point Patroni at all three:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;etcd3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10.0.0.11:2379,10.0.0.12:2379,10.0.0.13:2379&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use TLS for peer and client traffic in production; it is left out here for brevity.&lt;/p&gt;

&lt;p&gt;Now if &lt;code&gt;db1&lt;/code&gt; dies, &lt;code&gt;db2&lt;/code&gt; and the witness still form a majority (2 of 3), the leader key expires, and &lt;code&gt;db2&lt;/code&gt; can take it. If the link between &lt;code&gt;db1&lt;/code&gt; and &lt;code&gt;db2&lt;/code&gt; breaks but both can reach the witness, etcd still has one consistent view and only one node holds the key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 3: let the load balancers vote
&lt;/h2&gt;

&lt;p&gt;Applications rarely connect to PostgreSQL directly. In the common Patroni setup they go through HAProxy, which polls each node's Patroni REST API: &lt;code&gt;GET /primary&lt;/code&gt; returns HTTP 200 "only when the Patroni node is running as the primary with leader lock", and HAProxy sends writes to whichever node answers 200.&lt;/p&gt;

&lt;p&gt;That makes the load balancers useful observers. They see the cluster &lt;strong&gt;from the application side&lt;/strong&gt;. If most of them cannot reach the primary, the problem is not just the link between the two database nodes; clients are affected too. A design that counts load balancer opinions as votes can supply the third vote with machines you probably already run. This is not something Patroni does out of the box: you need a component that collects those observations, requires a strict majority, and then acts through Patroni (for example via its REST API) without ever bypassing etcd's view of who holds the leader key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to get right with two database nodes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Synchronous vs asynchronous replication.&lt;/strong&gt; With a single replica in strict synchronous mode, losing the replica stops writes on the primary. With asynchronous replication, a failover can lose the last committed transactions. Choose deliberately, and use Patroni's &lt;code&gt;maximum_lag_on_failover&lt;/code&gt; to keep a far-behind replica out of the election.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The frozen primary.&lt;/strong&gt; A primary that hangs (stuck disk, stopped process) without crashing keeps the leader key until it expires, and HAProxy will keep trying to send it writes. This is the scenario with the longest outage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fencing.&lt;/strong&gt; After a failover, the old primary must not receive writes again until it has rejoined as a replica.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test it.&lt;/strong&gt; Kill the primary, freeze it with &lt;code&gt;SIGSTOP&lt;/code&gt;, cut the network between the nodes, and measure how long writes stop. A failover you have never tested is an assumption.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Two database servers are fine. Two &lt;strong&gt;voters&lt;/strong&gt; are not.&lt;/li&gt;
&lt;li&gt;etcd needs 3 (or 5) members for fault tolerance; the third one does not have to run PostgreSQL.&lt;/li&gt;
&lt;li&gt;Your options for the third vote: a third DB node, an etcd-only witness, or a voting scheme built on the load balancers' view.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I work on HighAvailabilityData, a free on-premise console from NUTRADELIS S.L. that deploys and operates Patroni + etcd + HAProxy clusters over SSH. With two PostgreSQL nodes it uses the HAProxy instances and the console itself as the missing voters instead of a third database machine. The nodes remain standard Patroni clusters. More details in the &lt;a href="https://www.highavailabilitydata.com/resources/postgresql-high-availability-2-servers?lang=en" rel="noopener noreferrer"&gt;original version of this article&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Patroni, Dynamic Configuration Settings (&lt;code&gt;loop_wait&lt;/code&gt;, &lt;code&gt;ttl&lt;/code&gt;, &lt;code&gt;retry_timeout&lt;/code&gt;, &lt;code&gt;maximum_lag_on_failover&lt;/code&gt;): &lt;a href="https://patroni.readthedocs.io/en/latest/dynamic_configuration.html" rel="noopener noreferrer"&gt;https://patroni.readthedocs.io/en/latest/dynamic_configuration.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Patroni, REST API (&lt;code&gt;GET /primary&lt;/code&gt;): &lt;a href="https://patroni.readthedocs.io/en/latest/rest_api.html" rel="noopener noreferrer"&gt;https://patroni.readthedocs.io/en/latest/rest_api.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Patroni, DCS Failsafe Mode (demotion when the leader lock cannot be updated): &lt;a href="https://patroni.readthedocs.io/en/latest/dcs_failsafe_mode.html" rel="noopener noreferrer"&gt;https://patroni.readthedocs.io/en/latest/dcs_failsafe_mode.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;etcd FAQ (cluster size, majority, failure tolerance, odd number of members): &lt;a href="https://etcd.io/docs/next/faq/" rel="noopener noreferrer"&gt;https://etcd.io/docs/next/faq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HighAvailabilityData, original article: &lt;a href="https://www.highavailabilitydata.com/resources/postgresql-high-availability-2-servers?lang=en" rel="noopener noreferrer"&gt;https://www.highavailabilitydata.com/resources/postgresql-high-availability-2-servers?lang=en&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>devops</category>
      <category>sre</category>
    </item>
  </channel>
</rss>
