{"id":44794,"date":"2026-05-25T20:54:14","date_gmt":"2026-05-25T18:54:14","guid":{"rendered":"https:\/\/www.dbi-services.com\/blog\/?p=44794"},"modified":"2026-05-25T22:22:18","modified_gmt":"2026-05-25T20:22:18","slug":"surviving-a-patroni-failover-with-logical-replication","status":"publish","type":"post","link":"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/","title":{"rendered":"Surviving a Patroni failover with logical replication"},"content":{"rendered":"\n<p>Your Patroni cluster does exactly what you built it to do. A node dies at 3 a.m., a new primary is elected in a few seconds, the application reconnects, and nobody gets paged. Your HA setup works.<\/p>\n\n\n\n<p>And yet, somewhere downstream, your change-data-capture pipeline has just gone quiet. Debezium or Flink CDC was streaming every change out of that database into Kafka, into a data lake, into a search index. After the failover it stops. No crash, no alert, just a lag counter that climbs and a topic that never gets new records. By the time someone notices, you are missing hours of changes and the only honest fix is a full reseed.<\/p>\n\n\n\n<p>This is one of my favourite operational traps, because it sits in the blind spot between two teams that both think they did their job. The DBA built a solid HA cluster. The data engineer built a solid CDC pipeline. Nobody owned the join between them, and the join is a <strong>logical replication slot<\/strong>.<\/p>\n\n\n\n<p>From a DBA&#8217;s perspective this is a silent killer, and PostgreSQL 17 finally gave us a clean way to disarm it. Let me show you exactly why it breaks, what the old workarounds cost, and how native <strong>failover slots<\/strong> fix it, with a small Patroni lab you can run on Docker Desktop to see both the failure and the cure.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>If you only run Debezium:<\/strong> your half is one line, <code>slot.failover=true<\/code> on the connector (Debezium \u2265 3.0.5 against a PostgreSQL 17+ primary). The other half is server-side, two settings and the failover wiring, and it is your DBA&#8217;s job; the rest of this post is what to ask them for.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-why-the-slot-does-not-survive\">Why the slot does not survive<\/h2>\n\n\n\n<p>A logical replication slot is a bookmark. It records how far a consumer has read the write-ahead log (the WAL is PostgreSQL&#8217;s running log of every change; an LSN is just a position in it, like a byte offset), and it keeps PostgreSQL from recycling WAL the consumer still needs. Debezium, a native <code>CREATE SUBSCRIPTION<\/code>, <code>pg_recvlogical<\/code> all rely on a named slot to guarantee they never miss a change and never have to replay from the beginning.<\/p>\n\n\n\n<p>The issue is that a logical slot lives on only one instance, the one where it was created. Physical streaming replication, the mechanism Patroni uses to keep a standby in sync, copies the data blocks and the WAL. It does not copy the logical slots. For years that was a deliberate design decision, not a bug: a slot carries decoding state and a <code>catalog_xmin<\/code> (a do-not-garbage-collect marker for the old system-catalog rows it still needs to decode the WAL), and replaying that safely onto a standby is genuinely hard.<\/p>\n\n\n\n<p>So the picture before failover looks healthy:<\/p>\n\n\n\n<p><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"880\" height=\"498\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/05\/image-82.png\" alt=\"\" class=\"wp-image-44814\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/05\/image-82.png 880w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/05\/image-82-300x170.png 300w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/05\/image-82-768x435.png 768w\" sizes=\"auto, (max-width: 880px) 100vw, 880px\" \/><\/figure>\n\n\n\n<p>The slot exists only on the primary. Promote the standby, and the new primary has all your data and none of your slots. Debezium reconnects through your load balancer, asks for <code>cdc_slot<\/code>, and PostgreSQL answers, correctly, that no such slot exists. The pipeline is broken, and depending on how the connector is configured it either fails loudly or, worse, quietly recreates a fresh slot at the current position and skips everything in between.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-can-t-my-load-balancer-or-vip-just-handle-this\">Can&#8217;t my load balancer or VIP just handle this?<\/h2>\n\n\n\n<p>This is the first question I get, and it is a good one. The instinct is reasonable : I already have HAProxy in front of the cluster, or a VIP that floats with the leader on keepalived, or a cloud load balancer, so when the primary moves my consumer simply reconnects to the new one. Doesn&#8217;t that cover it?<\/p>\n\n\n\n<p>It does not, and understanding why tells you what class of problem this really is.<\/p>\n\n\n\n<p>A load balancer solves <strong>routing<\/strong>: how the consumer finds whoever is currently the primary. A floating VIP does the same thing one layer down. Both get the client to the right door, and you do need one of them (in the lab that is exactly HAProxy&#8217;s job). But the slot is not a routing concern, it is <strong>state<\/strong>. It is a file in the primary&#8217;s data directory. When the consumer reconnects through the VIP to the new primary and asks for <code>cdc_slot<\/code>, that instance still has to physically own the slot. The IP moving does not move the slot, because the new primary is a different PostgreSQL instance with its own <code>pg_replslot<\/code> directory. So whether HAProxy sits on the database nodes or on its own tier, and whether you use HAProxy or a VIP or a cloud LB, the effect on the slot is identical: none.<\/p>\n\n\n\n<p>There is exactly one architecture where the slot survives without any slot synchronization at all, and it is a different design rather than a different load balancer: <strong>shared-storage failover<\/strong>. If your HA is built on a block volume that detaches from the failed node and reattaches to the new one (DRBD, a SAN, a cloud disk, a Kubernetes PVC), then failover moves the whole data directory, <code>pg_replslot<\/code> included, and the slot comes along because it never logically moved. That works, but it buys a different set of trade-offs (the storage becomes a hard dependency and a fencing problem, and you give up streaming read replicas off the same volume), and it is not what a streaming-replication cluster like Patroni does. On streaming replication the slot has to be synchronized deliberately, which is the rest of this post.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-why-not-just-create-the-slot-on-every-node\">Why not just create the slot on every node?<\/h2>\n\n\n\n<p>The next idea people reach for is to pre-create the slot on every node, primary and standbys, and let the consumer use whichever one is currently primary. The intuition is right. In fact it is almost exactly what failover slots do, it just misses the one piece that makes it safe.<\/p>\n\n\n\n<p>A slot is not only a name. It carries moving state: <code>restart_lsn<\/code>, <code>confirmed_flush_lsn<\/code> and <code>catalog_xmin<\/code>. If you create independent slots on each node, each one starts at that node&#8217;s position and then sits there, because nothing advances the standby&#8217;s copy as the consumer acknowledges changes on the primary. The names match, the positions drift.<\/p>\n\n\n\n<p>That is worse than losing the slot, not better. After failover the consumer finds a slot called <code>cdc_slot<\/code> (so no loud error) and resumes from its stale position. If the copy is behind the consumer, you re-deliver changes it already processed, which is duplicates. If it is ahead, the consumer asks for an LSN the slot has already discarded, which is a gap or an error. You have turned a loud, obvious failure(&#8220;replication slot does not exist&#8221;, which at least pages someone) into a silent data-integrity problem, and for a pipeline feeding a data lake, silent is the worst outcome.<\/p>\n\n\n\n<p>Two more walls. Before PostgreSQL 16 you cannot even create a logical slot on a standby (you get &#8220;logical decoding cannot be used while in recovery&#8221;); version 16 added <a href=\"https:\/\/www.postgresql.org\/docs\/16\/logicaldecoding.html\">logical decoding on standbys<\/a>, and 17 added the synchronization that makes a standby slot actually track the primary. And idle slots are not free: every slot, active or not, pins WAL and holds back vacuum on its node, so leaving un-advanced slots on every node invites WAL growth.<\/p>\n\n\n\n<p>So the correct version of &#8220;a slot on every node, one active and the rest idle&#8221; is precisely a failover slot: present on the standby, not consumable until promotion, with its position kept in lockstep with the primary. The synchronization is the whole feature.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-years-before-postgresql-17\">The years before PostgreSQL 17<\/h2>\n\n\n\n<p>This problem is not new, and the community built two workarounds.<\/p>\n\n\n\n<p>The first was the <strong><a href=\"https:\/\/github.com\/EnterpriseDB\/pg_failover_slots\"><code>pg_failover_slots<\/code><\/a><\/strong> extension (from EDB), which runs on theprimary and copies logical slot positions out to physical standbys. It supportsPostgreSQL 11 and up, but from 17 on you would reach for the native feature instead,which is what EDB now recommends too. Either way it is an external extension you haveto install, load via<code>shared_preload_libraries<\/code>, and keep compatible with your major version.<\/p>\n\n\n\n<p>The second, if you run Patroni, is built in. Patroni has its own concept of <strong><a href=\"https:\/\/patroni.readthedocs.io\/en\/latest\/dynamic_configuration.html\">permanent replication slots<\/a><\/strong>. You declare a slot in the cluster configurationand Patroni makes sure it exists on every eligible node, copying the slot to thestandbys and advancing its position on a schedule. In Patroni terms it looks like this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: yaml; title: ; notranslate\" title=\"\">\nslots:\n  cdc_slot:\n    type: logical\n    database: cdcdemo\n    plugin: pgoutput\n<\/pre><\/div>\n\n\n<p>This genuinely survives a switchover, and for a lot of clusters it has been good enough for years. But notice the words &#8220;on a schedule&#8221;. Patroni advances the copy of the slot on the standby every <code>loop_wait<\/code> seconds, by default every 10 seconds. That periodic copy is the weak point. The standby&#8217;s idea of &#8220;how far the consumer has read&#8221; can lag the real primary by up to a loop interval, so a failover at the wrong moment can leave the new primary slightly behind what the consumer already acknowledged, which is exactly the window where you get duplicates or, with the wrong settings, a gap. It is a copy advanced on a timer, not a hard guarantee, so itis stale between refreshes and nothing stops the consumer from running ahead of it.<\/p>\n\n\n\n<p>That is the gap PostgreSQL 17 closes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-postgresql-17-failover-slots\">PostgreSQL 17 failover slots<\/h2>\n\n\n\n<p>PostgreSQL 17 introduced native <a href=\"https:\/\/www.postgresql.org\/docs\/current\/logical-replication-failover.html\"><strong>failover slots<\/strong><\/a>: logical slots that the database itself keeps synchronized to a physical standby as part of replication itself, with a guarantee that the consumer cannot read past what the standby has (the <code>synchronized_standby_slots<\/code> part below), rather than an external copy on a timer. (The feature landed in 17, so everything here applies to PostgreSQL 17 and later. I built the lab on PostgreSQL18, which adds a couple of conveniences I will mention at the end.)<\/p>\n\n\n\n<p>There are three moving parts.<\/p>\n\n\n\n<p><strong>1. Mark the slot as a failover slot.<\/strong> <a href=\"https:\/\/www.postgresql.org\/docs\/current\/functions-admin.html\"><code>pg_create_logical_replication_slot<\/code><\/a> grew a fifth argument,<code>failover<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nSELECT * FROM pg_create_logical_replication_slot(&#039;cdc_slot&#039;, &#039;pgoutput&#039;,\n                                                  false, false, true);\n--                                       temporary --^     ^      ^\n--                                          twophase ------+      |\n--                                          failover -------------+\n<\/pre><\/div>\n\n\n<p>If you drive logical replication with a subscription, the same flag exists there, <a href=\"https:\/\/www.postgresql.org\/docs\/current\/sql-createsubscription.html\"><code>CREATE SUBSCRIPTION ... WITH (failover = true)<\/code><\/a>, and the slot and the subscription must agree. If you use Debezium 3.x, you set <code>slot.failover = true<\/code> and the connector creates the slot with the flag for you, as long as it is talking to a PostgreSQL 17+ primary.<\/p>\n\n\n\n<p><strong>2. Let the standby synchronize slots.<\/strong> On the standby you enable the slot sync worker (both are <a href=\"https:\/\/www.postgresql.org\/docs\/current\/runtime-config-replication.html\">replication settings<\/a>):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nsync_replication_slots = on\nhot_standby_feedback   = on\n<\/pre><\/div>\n\n\n<p><code>hot_standby_feedback<\/code> is not optional here. The standby has to tell the primary which catalog rows it still needs so the primary does not vacuum away rows the synchronized slot depends on.<\/p>\n\n\n\n<p><strong>3. Hold the logical slot back until the standby has the data.<\/strong> On the primary you set <a href=\"https:\/\/www.postgresql.org\/docs\/current\/runtime-config-replication.html\"><code>synchronized_standby_slots<\/code><\/a>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nsynchronized_standby_slots = &#039;physical_slot_of_the_standby&#039;\n<\/pre><\/div>\n\n\n<p>This is the subtle, important one, a leash on the consumer. It tells the primary&#8217;s logical WAL senders never to hand a change to the CDC consumer until the physical standby has also received it, so the consumer can never get ahead of the standby. Without it, after failover the new primary cannot give the consumer what it already saw; with it, promotion is always safe.<\/p>\n\n\n\n<p>With those in place, the slot is mirrored in real time. On the standby, <a href=\"https:\/\/www.postgresql.org\/docs\/current\/view-pg-replication-slots.html\"><code>pg_replication_slots<\/code><\/a> shows it as synced, and it cannot be consumed until the standby is promoted:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n slot_name | slot_type | active | failover | synced | confirmed_flush_lsn\n-----------+-----------+--------+----------+--------+---------------------\n cdc_slot  | logical   | f      | t        | t      | 0\/3A1240\n<\/pre><\/div>\n\n\n<p><code>failover = t<\/code>, <code>synced = t<\/code>, <code>active = f<\/code>. That row is the whole point. When this standby becomes primary, <code>cdc_slot<\/code> is already there, at the right LSN, and the consumer reconnects as if nothing happened.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-gotcha-that-bites-everyone-silently\">The gotcha that bites everyone, silently<\/h3>\n\n\n\n<p>There is a prerequisite that is easy to miss and fails in the quietest possible way: the standby&#8217;s <code>primary_conninfo<\/code> must contain a <code>dbname<\/code> (the <a href=\"https:\/\/www.postgresql.org\/docs\/current\/logical-replication-failover.html\">logical replication failover docs<\/a> call this out, but it is easy to skip past).<\/p>\n\n\n\n<p>A physical replication connection does not need a database name, so historically nobody put one there. But the slot sync worker connects to the primary as a normal backend to read slot state, and it needs a database to connect to. If <code>dbname<\/code> is missing, the worker does not error in your face. It just does not sync anything. Your slots silently fail to appear on the standby, and you find out at failover, which is the worst possible time.<\/p>\n\n\n\n<p>Recent Patroni adds <code>dbname<\/code> to <code>primary_conninfo<\/code> for you when it is managing logical slots, but I check it explicitly, every time, and so should you, on the standby:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSHOW primary_conninfo;  \n<\/pre><\/div>\n\n\n<p><em>It must contain a dbname=\u2026 ; if not, slot sync is silently doing nothing<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-seeing-it-break-then-survive\">Seeing it break, then survive<\/h2>\n\n\n\n<p>Let me walk both cases on a small two-node Patroni cluster: PostgreSQL 18 nodes<code>patroni1<\/code> and <code>patroni2<\/code>, a Debezium connector reading through HAProxy (so it always follows the current primary), and a tiny generator inserting rows with a strictly increasing counter <code>n<\/code>. The proof is simple: compare the <code>n<\/code> values that reached Kafka against the rows actually in the table. Every committed row should be in Kafka; duplicates are fine, because CDC delivery is at-least-once.<\/p>\n\n\n\n<p>The commands below are the real thing, run them on your own cluster if you like. If you would rather watch it happen in a few minutes, there is a ready-to-run Docker version at the end of this post.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-first-the-break-a-plain-slot\">First, the break: a plain slot<\/h3>\n\n\n\n<p>A normal logical slot has <code>failover<\/code> off, the default. Debezium creates one for you when you register a connector with <code>slot.failover=false<\/code>; the bare SQL equivalent is:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT pg_create_logical_replication_slot(&#039;dbz_slot&#039;, &#039;pgoutput&#039;);\n<\/pre><\/div>\n\n\n<p>It lives on the primary and nowhere else, because nothing mirrors it:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n-- on the primary (patroni1)\nSELECT slot_name, slot_type, active, failover, synced FROM pg_replication_slots;\n\n slot_name | slot_type | active | failover | synced\n-----------+-----------+--------+----------+--------\n dbz_slot  | logical   | t      | f        | f\n patroni2  | physical  | t      | f        | f        (the standby&#039;s streaming slot)\n<\/pre><\/div>\n\n\n<p>On the standby, <code>dbz_slot<\/code> is simply not there. Now fail over with <code>patronictl<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\npatronictl -c \/etc\/patroni\/patroni.yml switchover --leader patroni1 --candidate patroni2 --force cdc-demo\n\nSuccessfully switched over to &quot;patroni2&quot;\n<\/pre><\/div>\n\n\n<p>The new primary never had the slot. The consumer reconnects through HAProxy and, here is the trap, stays perfectly green:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\ncurl -s localhost:8083\/connectors\/orders-plain\/status | jq &#039;{state: .connector.state, task: .tasks&#x5B;0].state}&#039;\n\n{ &quot;state&quot;: &quot;RUNNING&quot;, &quot;task&quot;: &quot;RUNNING&quot; }\n<\/pre><\/div>\n\n\n<p>because Debezium found its slot missing and silently recreated it at the <em>current<\/em> LSN, straight from the Connect log:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCreating replication slot with command CREATE_REPLICATION_SLOT &quot;dbz_slot&quot; LOGICAL pgoutput\n<\/pre><\/div>\n\n\n<p>Everything committed between the switchover and that recreation is skipped. Draining the topic and comparing it to the source table shows the hole:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ntable rows: 255    distinct rows in Kafka: 250    missing: 5   (n = 100..104)\n<\/pre><\/div>\n\n\n<p>Five committed rows, gone, and nothing anywhere raised an error. Whether Debezium recreates the slot quietly (as here) or fails loudly is a matter of configuration; the lab shows the quiet case because it is the one that hurts. That is the silent version that costs you a reseed three days later when someone notices the warehouse is short.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-then-the-cure-a-failover-slot\">Then, the cure: a failover slot<\/h3>\n\n\n\n<p>Same scenario, two changes. First, create the slot with <code>failover<\/code> on (or set Debezium&#8217;s <code>slot.failover=true<\/code>, which does it for you):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT pg_create_logical_replication_slot(&#039;cdc_slot&#039;, &#039;pgoutput&#039;, false, false, true);\n<\/pre><\/div>\n\n\n<p>Second, turn on synchronization. On the standby:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: yaml; title: ; notranslate\" title=\"\">\nsync_replication_slots = on\nhot_standby_feedback   = on\n<\/pre><\/div>\n\n\n<p>and on the primary, hold logical slots back until the standby has the data, naming the standby&#8217;s physical slot:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nALTER SYSTEM SET synchronized_standby_slots = &#039;patroni2&#039;;\nSELECT pg_reload_conf();\n<\/pre><\/div>\n\n\n<p>Now the slot is mirrored to the standby. Look at it there:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n-- on the standby (patroni2)\nSELECT slot_name, active, failover, synced, temporary, confirmed_flush_lsn\n  FROM pg_replication_slots WHERE slot_name = &#039;cdc_slot&#039;;\n\n slot_name | active | failover | synced | temporary | confirmed_flush_lsn\n-----------+--------+----------+--------+-----------+---------------------\n cdc_slot  | t      | t        | t      | f         | 0\/34B0A10\n<\/pre><\/div>\n\n\n<p><code>failover = t<\/code>, <code>synced = t<\/code>. One gotcha worth watching: a freshly synced slot starts <code>temporary = t<\/code> and is dropped on promotion until it persists. The standby log shows the transition, a transient refusal (its <code>catalog_xmin<\/code> is briefly ahead of the primary slot&#8217;s) and then sync-ready:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nLOG:  could not synchronize replication slot &quot;cdc_slot&quot;\nDETAIL:  ... the remote slot needs catalog xmin 760, but the standby has xmin 761.\nLOG:  newly created replication slot &quot;cdc_slot&quot; is sync-ready now\n<\/pre><\/div>\n\n\n<p>Only once <code>temporary = f<\/code> is it safe to fail over. So do it:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\npatronictl -c \/etc\/patroni\/patroni.yml switchover --leader patroni1 --candidate patroni2 --force cdc-demo\n<\/pre><\/div>\n\n\n<p>On the new primary the slot is already there and live, and the consumer resumes on its own. Re-point <code>synchronized_standby_slots<\/code> at the new standby (the correct value flips after a role change), then reconcile:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n-- on the new primary\nALTER SYSTEM SET synchronized_standby_slots = &#039;patroni1&#039;;\nSELECT pg_reload_conf();\n\ntable rows: 616    distinct rows in Kafka: 616    missing: 0   (74 duplicates)\n<\/pre><\/div>\n\n\n<p>Every committed row reached Kafka. Same cluster, same switchover, same consumer; the only difference is one boolean on the slot and the synchronization around it. The duplicates are at-least-once redelivery (you may see a handful or none on a given run), so the consumer must be idempotent either way, but not one committed row was lost. That boolean is the gap between &#8220;we lost four hours of CDC&#8221; and &#8220;nobody noticed&#8221;.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-patroni-does-its-part-but-not-all-of-it\">Patroni does its part, but not all of it<\/h2>\n\n\n\n<p>I want to be honest about the rough edges, because this is where the real-world write-ups tend to go quiet.<\/p>\n\n\n\n<p>PostgreSQL 17 gives you the mechanism. Patroni, as of the 4.1 series I tested, does <strong>not<\/strong> yet fully wire it for you, and there are two manual pieces.<\/p>\n\n\n\n<p>First, Patroni does not manage <code>synchronized_standby_slots<\/code> (this is tracked as Patroni issue #3431). You have to set it yourself, on the primary, to the physical slot name of the current standby. And in a two-node cluster that value is not symmetric: when <code>patroni1<\/code> leads it must name <code>patroni2<\/code>&#8216;s slot, and after a switchover to <code>patroni2<\/code> it must name <code>patroni1<\/code>&#8216;s slot. So you cannot just bake a single static value into the cluster config, you have to reset it after a role change. The lab&#8217;s <code>enable-failover-config.sh<\/code> is role-aware and does this, and the walkthrough deliberately runs it again after the switchover so you can see the value flip. In production you would automate it with a Patroni callback on role change.<\/p>\n\n\n\n<p>Second, the <code>dbname<\/code> in <code>primary_conninfo<\/code> requirement I mentioned. Recent Patroni handles it, but it is exactly the kind of thing that is true on the version you tested and silently false on the version you deployed, so verify it rather than trust it.<\/p>\n\n\n\n<p>Neither of these is a reason to avoid failover slots. They are a reason to treat &#8220;failover slots on Patroni&#8221; as a thing you configure and test deliberately, not a checkbox you assume.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-checklist\">The checklist<\/h2>\n\n\n\n<p>If you run logical replication or CDC on a Patroni cluster, here is what I would verify :<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Check<\/th><th>Where<\/th><th>Why<\/th><\/tr><\/thead><tbody><tr><td><code>wal_level = logical<\/code><\/td><td>all nodes<\/td><td>required for logical decoding at all<\/td><\/tr><tr><td>slot created with <code>failover = true<\/code><\/td><td>primary<\/td><td>the slot is eligible to be synchronized<\/td><\/tr><tr><td><code>sync_replication_slots = on<\/code><\/td><td>standbys<\/td><td>the standby actually runs the sync worker<\/td><\/tr><tr><td><code>hot_standby_feedback = on<\/code><\/td><td>standbys<\/td><td>protects the catalog rows the slot needs<\/td><\/tr><tr><td><code>synchronized_standby_slots<\/code> names the standby slot<\/td><td>primary<\/td><td>consumer never gets ahead of the standby<\/td><\/tr><tr><td><code>dbname=<\/code> present in <code>primary_conninfo<\/code><\/td><td>standbys<\/td><td>without it, sync fails silently<\/td><\/tr><tr><td><code>synced = t<\/code> <strong>and<\/strong> <code>temporary = f<\/code> on the standby<\/td><td>standbys<\/td><td>mirrored <strong>and<\/strong> persisted: a still-<code>temporary<\/code> synced slot is dropped on promotion (&#8220;sync-ready now&#8221;)<\/td><\/tr><tr><td>re-set <code>synchronized_standby_slots<\/code> after switchover<\/td><td>primary<\/td><td>the correct value flips in a 2-node cluster<\/td><\/tr><tr><td>your CDC tool sets the slot failover flag<\/td><td>consumer<\/td><td>e.g. Debezium <code>slot.failover = true<\/code>, needs \u2265 3.0.5 and PG 17+<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-where-this-is-going-pg18-today-pg19-next\">Where this is going: PG18 today, PG19 next<\/h2>\n\n\n\n<p>The lab runs on PostgreSQL 18, which keeps the PG17 mechanism intact and adds a few conveniences around it: <a href=\"https:\/\/www.postgresql.org\/docs\/current\/app-pgrecvlogical.html\"><code>pg_recvlogical --enable-failover<\/code><\/a> lets you create a failover slot straight from the command-line consumer, and <code>--enable-two-phase<\/code> (plus <code>pg_createsubscriber --enable-two-phase<\/code>) rounds out the tooling. The configuration is identical to 17, so everything above applies unchanged.<\/p>\n\n\n\n<p>PostgreSQL 19 is in development as I write this, so treat this as a direction, not a promise (check the final release notes). The theme is telling: most of the 19 cycle&#8217;s work here makes the failure I hit easier to <em>see<\/em> rather than changing the mechanism, slot-sync skip counters in <code>pg_stat_replication_slots<\/code>, a <code>pg_sync_replication_slots()<\/code>that no longer fails silently, starting logical replication without a <code>wal_level<\/code> restart (a new <code>effective_wal_level<\/code>), and logical replication of sequences.<\/p>\n\n\n\n<p>What 19 does not appear to change is the manual piece I complained about. <code>synchronized_standby_slots<\/code> still has to be set, and reset after a role change, by something outside PostgreSQL, and the Patroni side of that (issue #3431) is still open. So the checklist above does not get shorter in 19, it gets easier to debug.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-try-it-yourself\">Try it yourself<\/h2>\n\n\n\n<p>Everything above is in a lab attached to this post, <code>worklab-patroni-failover-slots<\/code>: a Docker Compose stack (etcd, two PostgreSQL 18 Patroni nodes, HAProxy, Kafka in KRaft mode, and a Debezium connector) plus a handful of helper scripts that wrap exactly the commands shown here. <\/p>\n\n\n\n<p>We spend a lot of effort making the database survive a node failure, and then we quietly assume everything hanging off the database survives with it. Logical replication slots are the reminder that high availability is not only about the primary coming back, it is about everything that depends on the primary coming back too.<\/p>\n\n\n\n<p>PostgreSQL 17 failover slots close the last real gap between PostgreSQL HA and the CDC pipelines we increasingly build on top of it. So don&#8217;t sleep too much on PG14 version (EOL November this year !). The mechanism is solid, the configuration is small, and the failure it prevents is the kind that is invisible until it is expensive. Clone the lab, break it once with a plain slot, fix it once with a failover slot, and you will never look at a Patroni switchover the same way again. When I started by DBA journey I have always been told that if you want to know how an RDBMS works playing around backups and restores would provide you deep insights into how they are running. I feel like today this is more true for replication. <\/p>\n\n\n\n<p><em><a href=\"https:\/\/github.com\/boutaga\/pgvector_RAG_search_lab\/tree\/main\/lab\/patroni-failover-cdc\" id=\"https:\/\/github.com\/boutaga\/pgvector_RAG_search_lab\/tree\/main\/lab\/patroni-failover-cdc\" target=\"_blank\" rel=\"noreferrer noopener\">Link for the scripts.<\/a><\/em><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Your Patroni cluster does exactly what you built it to do. A node dies at 3 a.m., a new primary is elected in a few seconds, the application reconnects, and nobody gets paged. Your HA setup works. And yet, somewhere downstream, your change-data-capture pipeline has just gone quiet. Debezium or Flink CDC was streaming every [&hellip;]<\/p>\n","protected":false},"author":153,"featured_media":37679,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[83],"tags":[614,4067,4068,4066,4065,1543,77],"type_dbi":[],"class_list":["post-44794","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-postgresql","tag-cdc","tag-debezium","tag-failover-slots","tag-high-availability-3","tag-logical-replication-2","tag-patroni","tag-postgresql"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.2 (Yoast SEO v27.6) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Surviving a Patroni failover with logical replication - dbi Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Surviving a Patroni failover with logical replication\" \/>\n<meta property=\"og:description\" content=\"Your Patroni cluster does exactly what you built it to do. A node dies at 3 a.m., a new primary is elected in a few seconds, the application reconnects, and nobody gets paged. Your HA setup works. And yet, somewhere downstream, your change-data-capture pipeline has just gone quiet. Debezium or Flink CDC was streaming every [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/\" \/>\n<meta property=\"og:site_name\" content=\"dbi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-05-25T18:54:14+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-05-25T20:22:18+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/03\/pixlr-image-generator-5f64d780-c578-477a-9419-7ddcdb807c83.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1024\" \/>\n\t<meta property=\"og:image:height\" content=\"1024\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Adrien Obernesser\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Adrien Obernesser\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"15 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/surviving-a-patroni-failover-with-logical-replication\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/surviving-a-patroni-failover-with-logical-replication\\\/\"},\"author\":{\"name\":\"Adrien Obernesser\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#\\\/schema\\\/person\\\/fd2ab917212ce0200c7618afaa7fdbcd\"},\"headline\":\"Surviving a Patroni failover with logical replication\",\"datePublished\":\"2026-05-25T18:54:14+00:00\",\"dateModified\":\"2026-05-25T20:22:18+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/surviving-a-patroni-failover-with-logical-replication\\\/\"},\"wordCount\":3267,\"commentCount\":0,\"image\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/surviving-a-patroni-failover-with-logical-replication\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2025\\\/03\\\/pixlr-image-generator-5f64d780-c578-477a-9419-7ddcdb807c83.png\",\"keywords\":[\"cdc\",\"debezium\",\"failover-slots\",\"high-availability\",\"logical-replication\",\"Patroni\",\"PostgreSQL\"],\"articleSection\":[\"PostgreSQL\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/surviving-a-patroni-failover-with-logical-replication\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/surviving-a-patroni-failover-with-logical-replication\\\/\",\"url\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/surviving-a-patroni-failover-with-logical-replication\\\/\",\"name\":\"Surviving a Patroni failover with logical replication - dbi Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/surviving-a-patroni-failover-with-logical-replication\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/surviving-a-patroni-failover-with-logical-replication\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2025\\\/03\\\/pixlr-image-generator-5f64d780-c578-477a-9419-7ddcdb807c83.png\",\"datePublished\":\"2026-05-25T18:54:14+00:00\",\"dateModified\":\"2026-05-25T20:22:18+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#\\\/schema\\\/person\\\/fd2ab917212ce0200c7618afaa7fdbcd\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/surviving-a-patroni-failover-with-logical-replication\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/surviving-a-patroni-failover-with-logical-replication\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/surviving-a-patroni-failover-with-logical-replication\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2025\\\/03\\\/pixlr-image-generator-5f64d780-c578-477a-9419-7ddcdb807c83.png\",\"contentUrl\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2025\\\/03\\\/pixlr-image-generator-5f64d780-c578-477a-9419-7ddcdb807c83.png\",\"width\":1024,\"height\":1024},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/surviving-a-patroni-failover-with-logical-replication\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Surviving a Patroni failover with logical replication\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/\",\"name\":\"dbi Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#\\\/schema\\\/person\\\/fd2ab917212ce0200c7618afaa7fdbcd\",\"name\":\"Adrien Obernesser\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/dc9316c729e50107159e0a1e631b9c1742ce8898576887d0103c83b1ca3bc9e6?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/dc9316c729e50107159e0a1e631b9c1742ce8898576887d0103c83b1ca3bc9e6?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/dc9316c729e50107159e0a1e631b9c1742ce8898576887d0103c83b1ca3bc9e6?s=96&d=mm&r=g\",\"caption\":\"Adrien Obernesser\"},\"url\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/author\\\/adrienobernesser\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Surviving a Patroni failover with logical replication - dbi Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/","og_locale":"en_US","og_type":"article","og_title":"Surviving a Patroni failover with logical replication","og_description":"Your Patroni cluster does exactly what you built it to do. A node dies at 3 a.m., a new primary is elected in a few seconds, the application reconnects, and nobody gets paged. Your HA setup works. And yet, somewhere downstream, your change-data-capture pipeline has just gone quiet. Debezium or Flink CDC was streaming every [&hellip;]","og_url":"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/","og_site_name":"dbi Blog","article_published_time":"2026-05-25T18:54:14+00:00","article_modified_time":"2026-05-25T20:22:18+00:00","og_image":[{"width":1024,"height":1024,"url":"http:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/03\/pixlr-image-generator-5f64d780-c578-477a-9419-7ddcdb807c83.png","type":"image\/png"}],"author":"Adrien Obernesser","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Adrien Obernesser","Est. reading time":"15 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/#article","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/"},"author":{"name":"Adrien Obernesser","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/fd2ab917212ce0200c7618afaa7fdbcd"},"headline":"Surviving a Patroni failover with logical replication","datePublished":"2026-05-25T18:54:14+00:00","dateModified":"2026-05-25T20:22:18+00:00","mainEntityOfPage":{"@id":"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/"},"wordCount":3267,"commentCount":0,"image":{"@id":"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/#primaryimage"},"thumbnailUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/03\/pixlr-image-generator-5f64d780-c578-477a-9419-7ddcdb807c83.png","keywords":["cdc","debezium","failover-slots","high-availability","logical-replication","Patroni","PostgreSQL"],"articleSection":["PostgreSQL"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/","url":"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/","name":"Surviving a Patroni failover with logical replication - dbi Blog","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/#primaryimage"},"image":{"@id":"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/#primaryimage"},"thumbnailUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/03\/pixlr-image-generator-5f64d780-c578-477a-9419-7ddcdb807c83.png","datePublished":"2026-05-25T18:54:14+00:00","dateModified":"2026-05-25T20:22:18+00:00","author":{"@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/fd2ab917212ce0200c7618afaa7fdbcd"},"breadcrumb":{"@id":"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/#primaryimage","url":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/03\/pixlr-image-generator-5f64d780-c578-477a-9419-7ddcdb807c83.png","contentUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2025\/03\/pixlr-image-generator-5f64d780-c578-477a-9419-7ddcdb807c83.png","width":1024,"height":1024},{"@type":"BreadcrumbList","@id":"https:\/\/www.dbi-services.com\/blog\/surviving-a-patroni-failover-with-logical-replication\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/www.dbi-services.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Surviving a Patroni failover with logical replication"}]},{"@type":"WebSite","@id":"https:\/\/www.dbi-services.com\/blog\/#website","url":"https:\/\/www.dbi-services.com\/blog\/","name":"dbi Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.dbi-services.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/fd2ab917212ce0200c7618afaa7fdbcd","name":"Adrien Obernesser","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/dc9316c729e50107159e0a1e631b9c1742ce8898576887d0103c83b1ca3bc9e6?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/dc9316c729e50107159e0a1e631b9c1742ce8898576887d0103c83b1ca3bc9e6?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/dc9316c729e50107159e0a1e631b9c1742ce8898576887d0103c83b1ca3bc9e6?s=96&d=mm&r=g","caption":"Adrien Obernesser"},"url":"https:\/\/www.dbi-services.com\/blog\/author\/adrienobernesser\/"}]}},"_links":{"self":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/44794","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/users\/153"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/comments?post=44794"}],"version-history":[{"count":41,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/44794\/revisions"}],"predecessor-version":[{"id":44836,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/44794\/revisions\/44836"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/media\/37679"}],"wp:attachment":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/media?parent=44794"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/categories?post=44794"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/tags?post=44794"},{"taxonomy":"type","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/type_dbi?post=44794"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}