<?xml version="1.0" encoding="UTF-8"?>
<feed
  xmlns="http://www.w3.org/2005/Atom"
  xmlns:thr="http://purl.org/syndication/thread/1.0"
  xml:lang="en-US"
  xml:base="https://mariadb.org/wp-atom.php">

  <title>MariaDB.org-Planet-Feed</title>
  <link type="application/atom+xml" href="https://mariadb.org/planet-atom" rel="self" />
  <link rel="alternate" href="https://mariadb.org/?page_id=24734" />
  <updated>2026-07-06T08:33:57+03:00</updated>
  <id>https://mariadb.org/planet-atom</id>

        <entry>
      <title>Cross-site Disaster Recovery with Percona Operator for MySQL</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/07/06/cross-site-disaster-recovery-with-percona-operator-for-mysql/" />
      <id>https://percona.community/blog/2026/07/06/cross-site-disaster-recovery-with-percona-operator-for-mysql/</id>
      <updated>2026-07-06T10:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>A MySQL InnoDB Cluster provides high availability for a single database cluster using Group Replication. This works well for node failures inside the cluster, but disaster recovery usually requires another cluster in a separate location: another Kubernetes cluster, region, data center, or cloud.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/07/06/cross-site-disaster-recovery-with-percona-operator-for-mysql/">Cross-site Disaster Recovery with Percona Operator for MySQL</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>A MySQL InnoDB Cluster provides high availability for a single database cluster using Group Replication. This works well for node failures inside the cluster, but disaster recovery usually requires another cluster in a separate location: another Kubernetes cluster, region, data center, or cloud.</p>
<p>This replica cluster needs to stay in sync with the primary, remain protected from accidental writes, and be ready to take over when you need to move traffic, either as a planned operation or during an outage.</p>
<p><a href="https://dev.mysql.com/doc/mysql-shell/8.0/en/innodb-clusterset.html" target="_blank" rel="noopener noreferrer">InnoDB ClusterSet</a> addresses this by linking multiple MySQL clusters into a single disaster-recovery topology. One cluster handles writes, while the others stay synchronized as read-only replicas.</p>
<p>Starting from v1.2.0, the Percona Operator for MySQL adds a new custom resource, <code>PerconaServerMySQLClusterSet</code>, which allows managing InnoDB ClusterSets. Creating the ClusterSet, adding replicas, switching the primary, and performing a forced failover are all handled declaratively by updating the Kubernetes spec and letting the operator reconcile the desired state.</p>
<p>This post explains how ClusterSet works, how to set it up with the Percona Operator, and how planned switchovers and emergency failovers work in practice.</p>
<h2 id="understanding-innodb-clusterset">Understanding InnoDB ClusterSet<a class="anchor-link" id="understanding-innodb-clusterset"></a></h2>
<p>Any disaster recovery design usually comes down to two important numbers:</p>
<ul>
<li><strong>Recovery Point Objective</strong>, or RPO, is how much data you can afford to lose. For example, an RPO of five seconds means the business can tolerate losing up to five seconds of writes.</li>
<li><strong>Recovery Time Objective</strong>, or RTO, is how long the system can be unavailable before service must be restored.</li>
</ul>
<p>The way you design and operate a ClusterSet directly affects both. To understand why, it helps to first look at the architecture.</p>
<p>An InnoDB ClusterSet is built from two or more InnoDB Clusters. Each InnoDB Cluster is a Group Replication group. In other words, it is the same kind of highly available MySQL cluster that the <a href="https://docs.percona.com/percona-operator-for-mysql/latest/index.html" target="_blank" rel="noopener noreferrer">Percona Operator for MySQL</a> can already deploy and manage.</p>
<p>A ClusterSet adds another layer on top of those clusters. One cluster is the primary cluster and accepts writes, while the others are replica clusters and remain read-only. The primary sends its changes to each replica using asynchronous replication over a dedicated replication channel.</p>
<p>This gives us two layers of replication, each solving a different problem.</p>
<p>Inside each cluster, Group Replication protects against the loss of individual MySQL nodes. Members are expected to be closer together, usually within the same region or availability zone group. Writes are coordinated by the group, which helps keep the local cluster consistent and highly available.</p>
<p>Between clusters, asynchronous replication protects against the loss of an entire site. Replica clusters can be located in another region, another Kubernetes cluster, or another cloud provider. Because this replication is asynchronous, long-distance network latency does not slow down writes on the primary cluster.</p>
<p>But the tradeoff here is that a replica cluster may be slightly behind the primary. The amount of lag depends on write volume, network latency, and the health of the replication channel. If the primary site is lost, any writes that had not yet reached the replica are lost. That lag is the practical data-loss window during an emergency failover. Any transactions that had not replicated before failover could be lost.</p>
<p>Before building a ClusterSet with the operator, there are a few important requirements to keep in mind:</p>
<ul>
<li>Every cluster in the ClusterSet must use the Group Replication topology. The operator also supports asynchronous replication with Orchestrator for standalone clusters, but that topology cannot be part of an InnoDB ClusterSet.</li>
<li>You need MySQL 8.0.27 or later</li>
<li>Clusters are linked by network address, not by Kubernetes references. A replica cluster only needs to be reachable and managed by an operator. It does not need to live in the same Kubernetes cluster as the primary.</li>
</ul>
<p>With the model in place, let&rsquo;s build a simple cross-site disaster recovery setup.</p>

<h2 id="setting-up-clusterset">Setting up ClusterSet<a class="anchor-link" id="setting-up-clusterset"></a></h2>
<p>We&rsquo;ll create the simplest useful ClusterSet: two Group Replication clusters named <code>dc1</code> and <code>dc2</code>.</p>
<p>In this example:<br>
<code>dc1</code> is the primary cluster.<br>
<code>dc2</code> is the read-only replica cluster.</p>
<p>In a real deployment, these would usually run in separate Kubernetes clusters, regions, or cloud environments. The steps are mostly the same. The main requirement is that the endpoints listed in the ClusterSet spec must be routable between sites.</p>
<h3 id="creating-a-primary-cluster">Creating a primary cluster<a class="anchor-link" id="creating-a-primary-cluster"></a></h3>
<p>The primary cluster <code>dc1</code> is a regular Group Replication cluster. There is nothing ClusterSet-specific about it at this stage.</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">yaml</span><button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-0">
<div class="highlight">
<pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">ps.percona.com/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">PerconaServerMySQL</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">dc1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">mysql</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">clusterType</span><span class="p">:</span><span class="w"> </span><span class="l">group-replication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c"># ... the rest of a normal cluster spec</span></span></span></code></pre>
</div>
</div>
</div>
<p>You can find a complete YAML <a href="https://github.com/percona/percona-server-mysql-operator/blob/main/deploy/cr.yaml" target="_blank" rel="noopener noreferrer">here</a>. Apply it and wait for it to come up the way you normally would, just as you would for any normal Percona Operator-managed MySQL cluster.</p>
<h3 id="creating-the-replica-cluster">Creating the replica cluster<a class="anchor-link" id="creating-the-replica-cluster"></a></h3>
<p>The replica cluster <code>dc2</code> is also a Group Replication cluster, but with one important difference:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">yaml</span><button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-1">
<div class="highlight">
<pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">ps.percona.com/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">PerconaServerMySQL</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">dc2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">mysql</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">clusterType</span><span class="p">:</span><span class="w"> </span><span class="l">group-replication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">bootstrap</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="l">manual </span><span class="w"> </span><span class="c"># &lt;- set this!</span></span></span></code></pre>
</div>
</div>
</div>
<p>Normally, when the operator creates a Group Replication cluster, the first MySQL pod bootstraps the group as soon as it starts. Subsequent pods then join that group.</p>
<p>For a ClusterSet replica, that is not what we want. We do not want <code>dc2</code> to form an independent empty cluster. Instead, we want it to receive data from the primary cluster and then join the ClusterSet as a replica.</p>
<p>With <code>bootstrap.mode: manual</code>, the first pod starts but does not bootstrap its own Group Replication group. It waits until the ClusterSet process adopts it, clones data from the primary, and then forms the replica cluster. During this stage, the first <code>dc2</code> pod may remain in a <code>NotReady</code> state until it is a part of the ClusterSet.</p>
<h3 id="sharing-cluster-credentials">Sharing cluster credentials<a class="anchor-link" id="sharing-cluster-credentials"></a></h3>
<p>The operator automatically creates a <code>clusterset</code> MySQL user in every cluster and stores its password in the cluster secret.</p>
<p>The operator uses this user to orchestrate ClusterSet operations, so the password must be the same across all clusters in the ClusterSet. When your clusters are deployed separately, copy the <code>clusterset</code> value from the primary cluster secret into the replica cluster secret before linking them.</p>
<p>For example, if <code>dc1</code> is the primary, copy the <code>clusterset</code> password from the <code>dc1</code> secret into the corresponding secret for <code>dc2</code>.</p>
<h3 id="linking-the-clusters">Linking the clusters<a class="anchor-link" id="linking-the-clusters"></a></h3>
<p>Once both clusters are applied, create a <code>PerconaServerMySQLClusterSet</code> custom resource.</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">yaml</span><button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-2">
<div class="highlight">
<pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">ps.percona.com/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">PerconaServerMySQLClusterSet</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-cluster-set</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">finalizers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">percona.com/clusterset-dissolve</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">primaryCluster</span><span class="p">:</span><span class="w"> </span><span class="l">dc1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">credentialsSecret</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">dc1-secrets</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">clusterset</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">sslMode</span><span class="p">:</span><span class="w"> </span><span class="l">AUTO</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">createReplicaClusterOptions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">recoveryMethod</span><span class="p">:</span><span class="w"> </span><span class="l">clone</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">clusters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">innodbClusterName</span><span class="p">:</span><span class="w"> </span><span class="l">dc1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">endpoints</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">dc1-mysql-primary.default.svc.cluster.local</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">innodbClusterName</span><span class="p">:</span><span class="w"> </span><span class="l">dc2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">endpoints</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">dc2-mysql-0.dc2-mysql.default.svc.cluster.local</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">mysqlshellRunner</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">perconalab/percona-server-mysql-operator:main-psmysql8.4</span></span></span></code></pre>
</div>
</div>
</div>
<p>The most important fields are:</p>
<ul>
<li><code>primaryCluster</code> defines which cluster currently accepts writes. The value must match one of the entries under clusters.</li>
<li><code>clusters</code> lists every member of the ClusterSet and the endpoint the operator should use to reach it. These endpoints are plain network addresses, which is what allows members to run in different Kubernetes clusters or regions.</li>
<li><code>credentialsSecret</code> points to the secret that contains the clusterset user password.</li>
<li><code>recoveryMethod: clone</code> tells the replica cluster to take a full copy of the primary data when it joins the ClusterSet. The alternative is an incremental recovery method, which uses existing binary logs instead of cloning the full dataset.</li>
<li><code>mysqlshellRunner</code> defines the helper pod image used by the operator to run MySQL Shell operations.</li>
</ul>
<p>After you apply this resource, the operator starts a MySQL Shell runner pod and creates the ClusterSet on <code>dc1</code>. It then joins <code>dc2</code>, which clones the data, starts replication, and brings up the remaining pods in the replica cluster.</p>
<p>At this point, <code>dc1</code> serves reads and writes, while <code>dc2</code> acts as a live read-only copy.</p>
<blockquote>
<p><strong>Seeding large replica clusters</strong></p>
<p>In this example, the replica cluster is created with <code>recoveryMethod: clone</code>, so MySQL Shell provisions the first replica member by copying a physical snapshot from an existing ClusterSet member. That is convenient for medium/small datasets, but it can be fragile across WAN links or very large databases.</p>
<p>A full clone can take hours, consume significant bandwidth, add load to the donor, run into network interruptions, and become expensive to retry if the operation fails partway through. It can also not be the best fit when the primary is busy or when cross-region egress cost is a concern.</p>
<p>The operator makes it possible to seed the replica cluster from an existing backup of the primary cluster instead. Create a <code>PerconaServerMySQLBackup</code> on the primary, restore that backup into the replica cluster with <code>PerconaServerMySQLRestore</code>, and then add the replica to the ClusterSet using <code>recoveryMethod: incremental</code>. You can find the exact restore procedure in the <a href="https://docs.percona.com/percona-operator-for-mysql/latest/backups-restore-to-new-cluster.html" target="_blank" rel="noopener noreferrer">documentation</a>.</p>
<p>At that point, the replica already has the primary&rsquo;s data and GTID history, so ClusterSet only needs to catch it up from the primary&rsquo;s binary logs instead of transferring the full dataset again.</p>
</blockquote>
<h3 id="verifying-it-worked">Verifying it worked<a class="anchor-link" id="verifying-it-worked"></a></h3>
<p>The simplest way to confirm that the ClusterSet is working is to write data to the primary cluster and read it from the replica.</p>
<p>For example:</p>
<ul>
<li>Connect to <code>dc1</code>.</li>
<li>Create a test table or insert a row.</li>
<li>Connect to <code>dc2</code>.</li>
<li>Confirm that the same data appears there.</li>
</ul>
<p>If the row appears on <code>dc2</code>, the asynchronous replication channel is running and the replica cluster is receiving changes from the primary.</p>
<h3 id="planned-switchover">Planned Switchover<a class="anchor-link" id="planned-switchover"></a></h3>
<p>A planned switchover is used when both clusters are healthy and you intentionally want to move writes from one site to another. This is useful for regional maintenance, Kubernetes cluster upgrades, cloud migrations, or controlled DR testing.</p>
<p>To move the primary role from <code>dc1</code> to <code>dc2</code>, update the primaryCluster field:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">shell</span><button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-3">
<div class="highlight">
<pre class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl patch ps-clusterset my-cluster-set --type<span class="o">=</span>merge <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -p <span class="s1">'{"spec":{"primaryCluster":"dc2"}}'</span></span></span></code></pre>
</div>
</div>
</div>
<p>The operator notices that the desired primary cluster no longer matches the current primary. It then uses MySQL Shell to perform a clean switchover.</p>
<p>Because both clusters are available, the operator can make sure the replica has caught up before changing roles. After the switchover completes, <code>dc2</code> becomes the writable primary and <code>dc1</code> becomes a read-only replica.</p>
<h3 id="emergency-failover">Emergency Failover<a class="anchor-link" id="emergency-failover"></a></h3>
<p>An emergency failover can be used when the primary cluster is unreachable and a clean handover is no longer possible.</p>
<p>This is the disaster recovery case: the Kubernetes cluster, region, or network path to the primary may be down, and you need to promote a surviving replica so the application can resume writes.</p>
<p>To fail over to <code>dc2</code>, update primaryCluster and explicitly set the forced failover flag:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">shell</span><button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-4">
<div class="highlight">
<pre class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl patch ps-clusterset my-cluster-set --type<span class="o">=</span>merge <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -p <span class="s1">'{"spec":{"primaryCluster":"dc2","unsafeFlags":{"forcedFailover":true}}}'</span></span></span></code></pre>
</div>
</div>
</div>
<p>The operator only follows this path when it can confirm that the current primary cluster is unreachable. It then promotes <code>dc2</code>, allowing it to accept writes.</p>
<p>The explicit flag is important because failover can cause data loss. Replication between clusters is asynchronous, so any writes that reached the old primary but had not yet replicated to <code>dc2</code> are not present on the new primary. Once <code>dc2</code> is promoted, those missing writes become unrecoverable through normal ClusterSet recovery.</p>
<p>The risk of data loss is why the field is named <code>unsafeFlags.forcedFailover</code>.</p>
<p>Another important point is that when the old primary comes back, it does not automatically resume as primary. After a forced failover, the recovered cluster must be explicitly reintroduced into the ClusterSet as a replica.</p>
<h3 id="adding-and-removing-clusters">Adding and removing clusters<a class="anchor-link" id="adding-and-removing-clusters"></a></h3>
<p>Adding or removing clusters follows the same declarative pattern: update the custom resource spec and let the operator reconcile the difference.</p>
<p>To add another replica cluster, add a new entry under clusters:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">yaml</span><button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-5">
<div class="highlight">
<pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">ps.percona.com/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">PerconaServerMySQLClusterSet</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-cluster-set</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c"># .. existing spec</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">clusters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c"># .. existing clusters</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">innodbClusterName</span><span class="p">:</span><span class="w"> </span><span class="l">dc3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">endpoints</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">dc3-mysql-primary.default.svc.cluster.local</span></span></span></code></pre>
</div>
</div>
</div>
<p>The operator joins the new cluster in the same way it joined <code>dc2</code>: it clones data from the primary, configures replication, and brings the cluster into the ClusterSet as a read-only replica.</p>
<p>To remove a cluster, delete its entry from the clusters list. You can update your manifest and reapply it, or use a JSON patch:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">shell</span><button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-6">
<div class="highlight">
<pre class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl patch ps-clusterset my-cluster-set --type<span class="o">=</span>json <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -p <span class="s1">'[{"op":"remove","path":"/spec/clusters/1"}]'</span></span></span></code></pre>
</div>
</div>
</div>
<p>If the cluster is healthy, the operator detaches it cleanly and it becomes a normal standalone cluster again.</p>
<p>If the cluster being removed is unreachable, you can force its removal:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">shell</span><button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-7">
<div class="highlight">
<pre class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl patch ps-clusterset my-cluster-set --type<span class="o">=</span>json -p <span class="s1">'[
</span></span></span><span class="line"><span class="cl"><span class="s1"> {"op":"remove","path":"/spec/clusters/1"},
</span></span></span><span class="line"><span class="cl"><span class="s1"> {"op":"add","path":"/spec/unsafeFlags/forcedClusterRemoval","value":true}
</span></span></span><span class="line"><span class="cl"><span class="s1">]'</span></span></span></code></pre>
</div>
</div>
</div>
<p>Like forced failover, forced removal is gated behind an unsafe flag because the operator should not make this decision silently. Removing an unreachable cluster from a ClusterSet is an operational decision with consequences, and it should be made explicitly.</p>
<h3 id="wrapping-up">Wrapping up<a class="anchor-link" id="wrapping-up"></a></h3>
<p>The Percona Operator for MySQL allows extending Group Replication beyond a single site by managing InnoDB ClusterSet through a custom resource <code>PerconaServerMySQLClusterSet</code>. A primary cluster handles writes, replica clusters stay synchronized, and the operator manages switchovers, failovers, and membership changes declaratively.</p>
<p>For planned maintenance, switchover moves the primary role safely with no data loss. For outages, forced failover promotes a surviving replica, with the expected risk of losing any writes that had not yet replicated. That replication lag is the practical RPO, so it should be monitored and tested as part of the DR plan.</p>
<p>With the Percona Operator for MySQL, disaster recovery becomes repeatable, Kubernetes-native, and easier to operate across regions or clusters.</p>
<h3 id="further-reading">Further reading<a class="anchor-link" id="further-reading"></a></h3>
<ul>
<li><a href="https://dev.mysql.com/doc/mysql-shell/8.0/en/innodb-clusterset.html" target="_blank" rel="noopener noreferrer">InnoDB ClusterSet docs</a></li>
<li><a href="https://docs.percona.com/percona-operator-for-mysql/latest/replication.html" target="_blank" rel="noopener noreferrer">Cross-site replication in Percona Operator for MySQL</a></li>
</ul>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/07/06/cross-site-disaster-recovery-with-percona-operator-for-mysql/">Cross-site Disaster Recovery with Percona Operator for MySQL</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Server Plugins: disabled functions</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-server-plugins-disabled-functions/" />
      <id>https://mariadb.org/mariadb-server-plugins-disabled-functions/</id>
      <updated>2026-07-06T08:33:57+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>During the last MariaDB Foundation Board Meeting (24 June 2026), Barry shared how it can be difficult to deploy an upgrade immediately and that they sometimes have to wait for one that fixes security bugs. …<br />
Continue reading \"MariaDB Server Plugins: disabled functions\"<br />
The post MariaDB Server Plugins: disabled functions appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-server-plugins-disabled-functions/">MariaDB Server Plugins: disabled functions</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>During the <a href="https://mariadb.org/bodminutes/2026-06-24/">last MariaDB Foundation Board Meeting</a> (24 June 2026), Barry shared how it can be difficult to deploy an upgrade immediately and that they sometimes have to wait for one that fixes security bugs. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-server-plugins-disabled-functions/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Server Plugins: disabled functions&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-server-plugins-disabled-functions/">MariaDB Server Plugins: disabled functions</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-server-plugins-disabled-functions/">MariaDB Server Plugins: disabled functions</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB 13.1 Feature in Focus: DENY / Negative Grants</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-13-1-feature-in-focus-deny-negative-grants/" />
      <id>https://mariadb.org/mariadb-13-1-feature-in-focus-deny-negative-grants/</id>
      <updated>2026-07-03T14:04:29+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>MariaDB 13.1 Preview is full of nice things.<br />
Some are immediately visible to developers, like the new JSON operators. Some are very useful to DBAs, such as configuration validation. …<br />
Continue reading \"MariaDB 13.1 Feature in Focus: DENY / Negative Grants\"<br />
The post MariaDB 13.1 Feature in Focus: DENY / Negative Grants appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-13-1-feature-in-focus-deny-negative-grants/">MariaDB 13.1 Feature in Focus: DENY / Negative Grants</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><a href="https://mariadb.org/download/?t=mariadb&amp;p=mariadb&amp;r=13.1.0&amp;os=Linux&amp;cpu=x86_64&amp;i=systemd&amp;mirror=bouwhuis">MariaDB 13.1</a> Preview is full of nice things.<br>
Some are immediately visible to developers, like the new JSON operators. Some are very useful to DBAs, such as configuration validation. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-13-1-feature-in-focus-deny-negative-grants/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB 13.1 Feature in Focus: DENY / Negative Grants&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-13-1-feature-in-focus-deny-negative-grants/">MariaDB 13.1 Feature in Focus: DENY / Negative Grants</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-13-1-feature-in-focus-deny-negative-grants/">MariaDB 13.1 Feature in Focus: DENY / Negative Grants</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Continuent joins MariaDB Foundation as a Silver Sponsor</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/continuent-joins-mariadb-foundation-as-a-silver-sponsor/" />
      <id>https://mariadb.org/continuent-joins-mariadb-foundation-as-a-silver-sponsor/</id>
      <updated>2026-07-03T05:07:29+00:00</updated>
      <author><name>Anna Widenius</name></author>
      <summary type="html"><![CDATA[<p>MariaDB Foundation is pleased to welcome Continuent as a new Silver Sponsor.<br />
Continuent develops solutions for organizations running business-critical applications on MariaDB and other MySQL-compatible databases. …<br />
Continue reading \"Continuent joins MariaDB Foundation as a Silver Sponsor\"<br />
The post Continuent joins MariaDB Foundation as a Silver Sponsor appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/continuent-joins-mariadb-foundation-as-a-silver-sponsor/">Continuent joins MariaDB Foundation as a Silver Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB Foundation is pleased to welcome <a href="https://www.continuent.com/">Continuent</a> as a new <a href="https://mariadb.org/donate/#silver-tier-from-eur-5000-per-year">Silver Sponsor.</a><br>
Continuent develops solutions for organizations running business-critical applications on MariaDB and other MySQL-compatible databases. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/continuent-joins-mariadb-foundation-as-a-silver-sponsor/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Continuent joins MariaDB Foundation as a Silver Sponsor&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/continuent-joins-mariadb-foundation-as-a-silver-sponsor/">Continuent joins MariaDB Foundation as a Silver Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/continuent-joins-mariadb-foundation-as-a-silver-sponsor/">Continuent joins MariaDB Foundation as a Silver Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Lowering the Barrier for MariaDB Plugin Development: Plugins in More Languages</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/lowering-the-barrier-for-mariadb-plugin-development-plugins-in-more-languages/" />
      <id>https://mariadb.org/lowering-the-barrier-for-mariadb-plugin-development-plugins-in-more-languages/</id>
      <updated>2026-07-03T04:42:07+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>MariaDB Server has long supported a flexible plugin architecture. Plugins allow developers to extend server functionality in areas such as data types, auditing, storage engines, information schema tables, and more. …<br />
Continue reading \"Lowering the Barrier for MariaDB Plugin Development: Plugins in More Languages\"<br />
The post Lowering the Barrier for MariaDB Plugin Development: Plugins in More Languages appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/lowering-the-barrier-for-mariadb-plugin-development-plugins-in-more-languages/">Lowering the Barrier for MariaDB Plugin Development: Plugins in More Languages</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB Server has long supported a flexible <a href="https://mariadb.org/plugins/">plugin architecture</a>. Plugins allow developers to extend server functionality in areas such as data types, auditing, storage engines, information schema tables, and more. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/lowering-the-barrier-for-mariadb-plugin-development-plugins-in-more-languages/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Lowering the Barrier for MariaDB Plugin Development: Plugins in More Languages&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/lowering-the-barrier-for-mariadb-plugin-development-plugins-in-more-languages/">Lowering the Barrier for MariaDB Plugin Development: Plugins in More Languages</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/lowering-the-barrier-for-mariadb-plugin-development-plugins-in-more-languages/">Lowering the Barrier for MariaDB Plugin Development: Plugins in More Languages</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Nextcloud renews its Silver sponsorship of MariaDB Foundation</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/nextcloud-renews-its-silver-sponsorship-of-mariadb-foundation/" />
      <id>https://mariadb.org/nextcloud-renews-its-silver-sponsorship-of-mariadb-foundation/</id>
      <updated>2026-07-02T21:58:37+00:00</updated>
      <author><name>Anna Widenius</name></author>
      <summary type="html"><![CDATA[<p>MariaDB Foundation is pleased to announce that Nextcloud has renewed its Silver sponsorship for another year.<br />
Nextcloud and MariaDB are widely used together by organisations that want greater control over their data and infrastructure. …<br />
Continue reading \"Nextcloud renews its Silver sponsorship of MariaDB Foundation\"<br />
The post Nextcloud renews its Silver sponsorship of MariaDB Foundation appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/nextcloud-renews-its-silver-sponsorship-of-mariadb-foundation/">Nextcloud renews its Silver sponsorship of MariaDB Foundation</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB Foundation is pleased to announce that <a href="https://nextcloud.com/">Nextcloud</a> has renewed its <a href="https://mariadb.org/donate/#silver-tier-from-eur-5000-per-year">Silver sponsorship</a> for another year.<br>
Nextcloud and MariaDB are widely used together by organisations that want greater control over their data and infrastructure. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/nextcloud-renews-its-silver-sponsorship-of-mariadb-foundation/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Nextcloud renews its Silver sponsorship of MariaDB Foundation&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/nextcloud-renews-its-silver-sponsorship-of-mariadb-foundation/">Nextcloud renews its Silver sponsorship of MariaDB Foundation</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/nextcloud-renews-its-silver-sponsorship-of-mariadb-foundation/">Nextcloud renews its Silver sponsorship of MariaDB Foundation</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Still on MySQL 5.7 or 8.0? Those high-severity CVE fixes are covered</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/mysql-8-0-eol-support-cve-fixes-covered/" />
      <id>https://www.percona.com/blog/mysql-8-0-eol-support-cve-fixes-covered/</id>
      <updated>2026-07-02T08:01:50+00:00</updated>
      <author><name>Dennis Kittrell</name></author>
      <summary type="html"><![CDATA[<p>Upstream MySQL published an out-of-schedule release this week with two high-severity CVE fixes. If you’re running Percona Server for MySQL 5.7 or 8.0 under Extended Lifecycle Support (ELS), the program we previously called Post EOL Support, you don’t have to do anything to qualify for them. We’ve already applied the fixes and re-released the affected … Continued<br />
The post Still on MySQL 5.7 or 8.0? Those high-severity CVE fixes are covered appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/mysql-8-0-eol-support-cve-fixes-covered/">Still on MySQL 5.7 or 8.0? Those high-severity CVE fixes are covered</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Upstream MySQL published an out-of-schedule release this week with two high-severity CVE fixes. If you&rsquo;re running Percona Server for MySQL 5.7 or 8.0 under Extended Lifecycle Support (ELS), the program we previously called Post EOL Support, you don&rsquo;t have to do anything to qualify for them. We&rsquo;ve already applied the fixes and re-released the affected ELS builds.</p>
<p>This is the point of ELS. When a major version reaches End of Life (EOL), the community stops shipping patches, but the databases running on it don&rsquo;t stop mattering. ELS keeps critical bug and security fixes coming for versions that are past their EOL date, so you can stay on 5.7 or 8.0 on your own timeline instead of a deadline someone else set.</p>
<h2>What we did<a class="anchor-link" id="what-we-did"></a></h2>
<p>These CVE fixes landed upstream outside the normal cadence. Under ELS, customers are entitled to security fixes for the versions they run, so we pulled the patches into the 5.7 and 8.0 builds and re-released them. ELS customers can pull the updated builds from the usual private repository.</p>
<h2>Why this matters if you&rsquo;re still on 5.7 or 8.0<a class="anchor-link" id="why-this-matters-if-youre-still-on-5-7-or-8-0"></a></h2>
<p>Percona Server for MySQL 5.7 reached EOL in October 2023. Percona Server for MySQL 8.0 reached EOL in April 2026. Plenty of production systems are still on both, and not every migration can happen on the upstream&rsquo;s schedule. Running an unpatched database past EOL is where the real risk sits: no security fixes, no bug fixes, and no support when something breaks at 2:00 a.m.</p>
<p>ELS closes that gap. You keep getting the critical fixes, including out-of-schedule security patches like these, while you plan an upgrade on terms that work for your team.</p>
<h2>Where to go from here<a class="anchor-link" id="where-to-go-from-here"></a></h2>
<p>If you&rsquo;re on 5.7 or 8.0 and don&rsquo;t have ELS in place, now is a good time to look at it. The fixes we just shipped are exactly what the program is for. See the details for your version: <a href="https://www.percona.com/mysql-8-0-eol-support/">Extended Lifecycle Support for MySQL 8.0</a> or <a href="https://www.percona.com/post-mysql-5-7-eol-support/">Extended Lifecycle Support for MySQL 5.7</a>. Or reach out via <a href="http://percona.com">percona.com</a> or the Percona Community Forum to discuss coverage for your environment.</p>
<p>&nbsp;</p>
<hr>
<p><span class="notion-enable-hover" data-token-index="0">Written by </span><span class="notion-text-mention-token notion-enable-hover notion-focusable-token" data-token-index="1">@Dennis Kittrell</span><span class="notion-enable-hover" data-token-index="2"> &ndash; Reviewed by </span><span class="notion-text-mention-token notion-enable-hover notion-focusable-token" data-token-index="3">@Matthew Boehm</span><span class="notion-enable-hover" data-token-index="4"> &amp; </span><span class="notion-text-mention-token notion-enable-hover notion-focusable-token" data-token-index="5">@Varun Nagaraju</span> <!-- notionvc: 48fbd903-e255-42ad-8db1-f691698fae89 --></p>
<p><!-- notionvc: f563df38-9d48-4c17-a7b8-cf1211d095a0 --></p>
<p>The post <a href="https://www.percona.com/blog/mysql-8-0-eol-support-cve-fixes-covered/">Still on MySQL 5.7 or 8.0? Those high-severity CVE fixes are covered</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/mysql-8-0-eol-support-cve-fixes-covered/">Still on MySQL 5.7 or 8.0? Those high-severity CVE fixes are covered</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>PostgreSQL Autovacuum Internals and Benchmark</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/07/01/postgresql-autovacuum-internals-benchmark/" />
      <id>https://percona.community/blog/2026/07/01/postgresql-autovacuum-internals-benchmark/</id>
      <updated>2026-07-01T11:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>PostgreSQL Autovacuum Internals and Benchmark Introduction Vacuum, or more precisely autovacuum, is the most important automatic maintenance task in PostgreSQL. It is key for performance, but also for long-term database survival. If it runs too often, it can damage performance. If it does not run often enough, performance can suffer. With too few workers, it takes too long. With too many, it consumes resources. If the maintenance work memory is not enough, the load can multiply due to multiple index scans. If you disable it completely, it will rise from the dead and run without limits.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/07/01/postgresql-autovacuum-internals-benchmark/">PostgreSQL Autovacuum Internals and Benchmark</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<h1 id="postgresql-autovacuum-internals-and-benchmark">PostgreSQL Autovacuum Internals and Benchmark<a class="anchor-link" id="postgresql-autovacuum-internals-and-benchmark"></a></h1>
<h2 id="introduction">Introduction<a class="anchor-link" id="introduction"></a></h2>
<p>Vacuum, or more precisely autovacuum, is the most important automatic maintenance task in PostgreSQL. It is key for performance, but also for long-term database survival. If it runs too often, it can damage performance. If it does not run often enough, performance can suffer. With too few workers, it takes too long. With too many, it consumes resources. If the maintenance work memory is not enough, the load can multiply due to multiple index scans. If you disable it completely, it will rise from the dead and run without limits.</p>
<p>I guess you get it. It is critical to understand what autovacuum does and how it does it.</p>
<p>Autovacuum is triggered when certain row count thresholds are crossed. In the final part of this post we describe a benchmark we run to validate if modified rows is the right approach to trigger automatic vacuum execution or we should consider something different like page based thresholds. We will also measure the impact index in vacuum.</p>
<p>This blog post explains how autovacuum works, but some previous basic understanding of PostgreSQL internals is required.</p>
<p>Here are the terms you&rsquo;ll need, feel free to skip if you already know them:</p>
<ul>
<li><strong>MVCC (multi-version concurrency control)</strong>: Rather than overwrite a row, PostgreSQL keeps multiple versions of it. This is used to provide consistent views to the different transactions running at the same time, which is why obsolete row versions pile up when there are long running transactions and/or tables are not vacuumed. MVCC is used by transactions to determine row visibility.</li>
<li><strong>Tuple</strong>: One on-disk version of a row. A row updated three times leaves behind three tuples.</li>
<li><strong>Dead tuple</strong>: A tuple that is not visible to any transaction. Reclaiming these is the vacuum&rsquo;s main job.</li>
<li><strong>Heap</strong>: A table&rsquo;s main structure, where the tuples live. Indexes are separate structures.</li>
<li><strong>Page (block)</strong>: The 8 KB unit PostgreSQL reads and writes. A heap is an array of pages. If a page is dirty, it means it contains data that hasn&rsquo;t been written to disk yet.</li>
<li><strong>TID</strong>: A tuple&rsquo;s address: which page, which slot. Inside of the pages there is an array that points to the actual row position in the page, the slot is the position in that array. This way row space inside of the page can be reorganized without changing the TID. Index entries are TIDs pointing into the heap.</li>
<li><strong>Vacuum</strong>: The operation that removes dead tuples (and does a few things more).</li>
<li><strong>Autovacuum</strong>: Vacuum that PostgreSQL runs for you, in the background, on its own schedule.</li>
<li><strong>Visibility map (VM)</strong>: A small per-table bitmap flagging which pages have all tuples visible or frozen (see freezing).</li>
<li><strong>Freezing</strong>: Stamping old tuples as permanently visible, so their transaction IDs are no longer relevant (see Transaction ID wraparound). Here permanent is a bit misleading, if the row is modified, the permanent tuple will become a dead tuple and will be removed by vacuum.</li>
<li><strong>Transaction ID (XID) wraparound</strong>: Each transaction is assigned an ID. This ID identifies which transactions modified which rows and is thus critical for visibility. The problem is that the transaction counter is finite and eventually wraps around. To avoid problems, older rows must be marked as permanently visible (frozen), this way their transaction id becomes irrelevant.</li>
<li><strong>Bloat</strong>: Space allocated by dead tuples, as dead tuples are not visible, it is wasted space. Vacuum works to reduce it, but can&rsquo;t always reverse it.</li>
<li><strong>Shared buffers</strong>: PostgreSQL&rsquo;s in-memory page cache. Nearly all reads and writes pass through it.</li>
<li><strong>WAL (write-ahead log)</strong>: Every change is logged here before it touches a data page, so the database can recover after a crash.</li>
<li><strong>Checkpoint</strong>: The point at which modified (&ldquo;dirty&rdquo;) pages in shared buffers are written out to the data files.</li>
</ul>
<h2 id="launcher--worker-architecture">Launcher &amp; Worker Architecture<a class="anchor-link" id="launcher-worker-architecture"></a></h2>
<p>The <strong>autovacuum launcher</strong> is a background process that starts autovacuum workers. Its goal is to start one worker per database every <code>autovacuum_naptime</code> seconds (default: 1 min). With N databases, this means the launcher starts a new worker roughly every <code>autovacuum_naptime / N</code> seconds, round-robin across databases. But it is not the launcher that starts the workers. It requests the postmaster to fork an <strong>autovacuum worker</strong> for the chosen database.</p>
<p>Workers, once spawned, run independently until they finish all eligible tables in their assigned database and then exit. Up to <code>autovacuum_max_workers</code> (default 3) workers can run concurrently, and there is no restriction on how many of those may be in the same database. If a database has many tables that need vacuuming, it can run multiple concurrent vacuum workers. In this case, workers coordinate to avoid vacuuming the same table.</p>
<p>A database approximately has a worker assigned every &ldquo;nap time seconds&rdquo; or later. A worker is assigned even if there are no tables requiring vacuum.</p>
<h2 id="table-selection--prioritization">Table Selection &amp; Prioritization<a class="anchor-link" id="table-selection-prioritization"></a></h2>
<p>This is the process inside a worker:</p>
<ol>
<li>Scans <code>pg_class</code> to enumerate all tables in the database, then fetches per-relation statistics (dead tuple counts, etc.) from the cumulative statistics system (pgstat) for each one.</li>
<li>Compares each table&rsquo;s dead-tuple count against the vacuum threshold (see the formula below).</li>
<li>Also checks if the table needs an ANALYZE (separate threshold).</li>
<li>Also checks for <strong>anti-wraparound</strong>: if <code>pg_class.relfrozenxid</code> age exceeds <code>autovacuum_freeze_max_age</code> (default 200M transactions), or if <code>pg_class.relminmxid</code> age exceeds <code>autovacuum_multixact_freeze_max_age</code> (default 400M), the table is vacuumed regardless of all thresholds and even <code>autovacuum_enabled = off</code> on the table.</li>
</ol>
<h3 id="prioritization">Prioritization<a class="anchor-link" id="prioritization"></a></h3>
<p>There is no table-level priority sorting in a database. A worker vacuums tables in the order they are collected from the <code>pg_class</code> scan. <code>do_autovacuum()</code> in <code>src/backend/postmaster/autovacuum.c</code> iterates the <code>table_oids</code> list directly. The worker claims each table sequentially by marking it as &ldquo;mine&rdquo; in shared memory (this triggers a brief lock to the memory structure so two workers don&rsquo;t pick the same table at the same time), and calls <code>table_recheck_autovac()</code> to re-read catalog/pgstat and confirm the table still needs work (as it could have been already vacuumed by another worker). Anti-wraparound urgency is handled one level up, at database selection: the launcher&rsquo;s <code>do_start_worker()</code> preferentially dispatches a worker to whichever database is closest to the wraparound limit. So there is no dead-tuple-count-based ordering of tables. Within a database, processing order is effectively catalog order.</p>
<h2 id="how-vacuum-finds-pages-to-process">How Vacuum Finds Pages to Process<a class="anchor-link" id="how-vacuum-finds-pages-to-process"></a></h2>
<p>Once a table is selected, the worker doesn&rsquo;t blindly scan every page. It uses the <strong>visibility map</strong> (VM) to skip pages that do not need vacuuming.</p>
<h3 id="the-visibility-map">The Visibility Map<a class="anchor-link" id="the-visibility-map"></a></h3>
<p>Every table has an associated visibility map, a bitmap with two bits per heap page:</p>
<ol>
<li>All-visible bit: every tuple on the page is visible to all current and future transactions. During a normal (non-aggressive) vacuum, this page can generally be skipped. There are no dead tuples to reclaim. However, even all-visible pages may be visited in some cases, such as for eager freezing or readahead optimization (pages are read sequentially even if some of them are not needed).</li>
<li>All-frozen bit: every tuple on the page is frozen, marked with the <code>HEAP_XMIN_FROZEN</code> infomask bits (since PostgreSQL 9.4, the value of <code>xmin</code> is <strong>preserved</strong> for forensics rather than physically overwritten with <code>FrozenTransactionId</code> although a lot of people still think the xmin is changed). The page can be skipped even during aggressive/anti-wraparound vacuum. An aggressive vacuum must visit all pages that are <em>not</em> all-frozen to freeze as many tuples as possible.</li>
</ol>
<p>The VM makes vacuuming efficient because it reduces the number of pages to visit while searching for dead tuples. If a 10GB table has dead tuples on only 50 pages, vacuum reads the VM (around 320KB for a 10GB heap, 2 bits per 8KB page) and then focuses on those 50 pages rather than the full 10GB. We already mentioned that a normal vacuum can also visit some additional pages for eager freezing or readahead, but the VM still eliminates the vast majority of random I/O.</p>
<p>Visibility-map bits are cleared by backends running DML statements and usually set by autovacuum workers or manually triggered vacuum operations.</p>
<p>The VM is also used by index-only scans to determine whether visiting the heap page to validate tuple visibility is needed. If the page in the VM is marked as &ldquo;all-visible,&rdquo; then visibility checks are not required and we don&rsquo;t need that extra access, improving performance significantly.</p>
<h3 id="the-scan-process">The Scan Process<a class="anchor-link" id="the-scan-process"></a></h3>
<p>The worker performs a <strong>sequential scan of the heap</strong>, but guided by the VM:</p>
<ol>
<li>Read the VM to identify pages that are NOT all-visible and may contain dead tuples.</li>
<li>For each such page, read it into shared buffers (if not already there).</li>
<li>Examine each tuple&rsquo;s header (<code>t_xmin</code>, <code>t_xmax</code>, <code>t_infomask</code>) to determine if the tuple is dead, meaning it was deleted or updated, and no running transaction can see it anymore.</li>
<li>Dead tuples are collected into an in-memory <strong>dead-TID store</strong>, since PG17 a <code>TidStore</code>, a compact adaptive-radix-tree keyed by block number that replaced the old sorted <code>ItemPointer</code> array and its hard 1 GB cap. Its size is bounded by <code>autovacuum_work_mem</code> (default -1, which falls back to <code>maintenance_work_mem</code>, default 64MB). For manual <code>VACUUM</code>, <code>maintenance_work_mem</code> is used directly.</li>
<li>If the work memory fills up before the table is fully scanned, the worker pauses the heap scan, processes the accumulated dead tuples (index cleanup + heap cleanup), then resumes the heap scan from where it stopped. This means a single vacuum of a large, heavily updated table may involve multiple passes through the indexes.</li>
</ol>
<h3 id="limiting-cache-impact-with-the-buffer-ring">Limiting Cache Impact with the Buffer Ring<a class="anchor-link" id="limiting-cache-impact-with-the-buffer-ring"></a></h3>
<p>The step 2 above says &ldquo;read the heap page into shared buffers&rdquo;), but if vacuum has to pull every page it scans into <code>shared_buffers</code>, vacuuming a large table would evict the pages that other queries depend on, trashing the cache during a maintenance task. PostgreSQL prevents this with a <strong>buffer access strategy</strong>, commonly called a <strong>ring buffer</strong>.</p>
<p>Rather than allocating pages all over the shared pool, vacuum uses a small <strong>ring</strong> of shared pool pages that it reuses circularly: when it needs a buffer for a new page, and the ring is full, it recycles the oldest buffer in the ring instead of claiming another from <code>shared_buffers</code>. If vacuum needs a page already in the shared pool, that page is not added to the ring. The ring size is set by <code>vacuum_buffer_usage_limit</code>, default 2 MB in PG18 (256 buffers of 8 KB), ranges from 128 kB to 16 GB, with a limit of 1/8 of <code>shared_buffers</code> (you can set it higher, but it will limited to that value). A value of <code>0</code> disables the ring entirely, letting vacuum use as much of <code>shared_buffers</code> as it needs. The same limit applies to <code>ANALYZE</code> and to autovacuum (which runs the same vacuum code). The <code>VACUUM</code> command accepts a per-statement <code>BUFFER_USAGE_LIMIT</code> option.</p>
<p>The ring has consequences: <strong>When the buffer being recycled is still dirty, vacuum must write it out before reusing the slot</strong>. As WAL is written before the page, we have to flush any outstanding WAL for that page first. So once vacuum dirties more pages than the ring can hold, it begins doing <strong>its own writes</strong> inline rather than leaving them all for the checkpointer or background writer. This may look as a trade-off as the ring caps vacuum&rsquo;s cache footprint, but requires vacuum to perform some of its own write-back (and WAL flushing) as it runs. But if a page was read into the ring, probably that pages was not very active and will not be read again soon. Raising <code>vacuum_buffer_usage_limit</code> (or setting it to <code>0</code>) relaxes the limit: a faster vacuum. But a faster vacuum that will evict more active pages and later will require more work by the checkpointer.</p>
<h3 id="determining-tuple-liveness">Determining Tuple Liveness<a class="anchor-link" id="determining-tuple-liveness"></a></h3>
<p>For each tuple on a non-all-visible page, vacuum checks:</p>
<ul>
<li><code>t_xmin</code> (inserting transaction identified): Check whether it committed. If the inserting transaction aborted, the tuple is dead immediately.</li>
<li><code>t_xmax</code> (deleting/updating transaction identifier): Check whether it committed and is older than the oldest running transaction (<code>OldestXmin</code>). If so, no active transaction can see this tuple version and the tuple is dead.</li>
<li>Vacuum, like the other backends, reads <code>pg_xact</code> (the commit log / CLOG) to determine transaction commit status, and sets <strong>hint bits</strong> (<code>HEAP_XMIN_COMMITTED</code>, <code>HEAP_XMAX_COMMITTED</code>, etc.) on tuple headers so future accesses don&rsquo;t need to re-check <code>pg_xact</code>. Changing the hint bits marks the page as dirty, but does not save that change in the WAL, unless specified in the configuration (checksums enabled or wal_log_hints). The purpose of the hint bits is help future transactions know the outcome of the inserting/modifying transactions without checking the commit log.</li>
</ul>
<p><code>OldestXmin</code> is the oldest transaction ID that any running transaction might still need to see, also known sometimes as the xmin horizon. Tuples deleted or replaced by transactions newer than <code>OldestXmin</code> <strong>cannot be vacuumed</strong> because some active transactions might still need them. This is why long-running transactions limit the space that vacuum can reclaim.</p>
<h2 id="what-happens-to-heap-pages">What Happens to Heap Pages<a class="anchor-link" id="what-happens-to-heap-pages"></a></h2>
<p>Once dead tuples are identified on a page:</p>
<ol>
<li>Dead tuple line pointers are set to <code>LP_DEAD</code> during the heap scan phase. Later, after index cleanup removes all dangling index references, vacuum performs a second heap pass that converts these to <code>LP_UNUSED</code>, making the slots available for reuse. (For tables with no indexes, vacuum can mark <code>LP_UNUSED</code> immediately since there are no index pointers to worry about.)</li>
<li>The page is compacted. Live tuples are shuffled toward the high end of the page, and free space is consolidated in the middle (between the line pointer array and the tuple data area). This is called <strong>page pruning/defragmentation</strong>. It updates the page&rsquo;s <code>pd_lower</code> (end-of-line pointers) and <code>pd_upper</code> (start-of-tuple data) to reflect the new free space.</li>
<li>The page is marked dirty in shared buffers. It will be written back to disk by the background writer, at the next checkpoint or if the ring buffer is full and vacuum needs that space. This is the <code>vacuum_cost_page_dirty</code> cost event (adds 20 to the cost global cost of running vacuum operations).</li>
<li>The VM is updated. If, after removing dead tuples, every remaining tuple on the page is visible to all transactions, the all-visible bit is set. During aggressive/anti-wraparound vacuum, if all tuples are also frozen, the all-frozen bit is set.</li>
<li>The FSM (Free Space Map) tree is updated periodically (every <code>VACUUM_FSM_EVERY_PAGES</code> pages or after heap/index cleanup pass, not after every individual page is added to the FSM) to advertise newly available space, so future DML operations can reuse it.</li>
</ol>
<h3 id="heap-truncation">Heap Truncation<a class="anchor-link" id="heap-truncation"></a></h3>
<p>After processing all pages, vacuum checks whether the <strong>last pages</strong> of the heap file are entirely empty (all dead tuples were removed and there are no live tuples). If so, it <strong>truncates the file</strong>, physically shrinking it and returning disk space to the OS. This is the only situation where vacuum reduces the on-disk size of a table. The space reclaimed in the middle of the file is reused, not returned to the filesystem.</p>
<p>Truncation requires an <strong>AccessExclusiveLock</strong> lock during the truncation, which can cause a short stall on concurrent access. Table truncation can be disabled per-table or globally with <code>vacuum_truncate = off</code>.</p>
<h2 id="index-cleanup">Index Cleanup<a class="anchor-link" id="index-cleanup"></a></h2>
<p>Index cleanup is also required and is often an expensive part of vacuum. Indexes must be cleaned because they contain pointers (TIDs) to heap tuples. If the corresponding heap tuple is dead, the index entry becomes a <strong>dangling pointer</strong> and must be removed.</p>
<h3 id="the-process">The Process<a class="anchor-link" id="the-process"></a></h3>
<ol>
<li>After the heap scan (or after <code>maintenance_work_mem</code> fills), vacuum has its dead-TID store populated (block-ordered).</li>
<li>For <strong>each index</strong> on the table, vacuum calls the index access method&rsquo;s <code>ambulkdelete</code> function. For B-tree indexes, this invokes <code>btbulkdelete()</code> then <code>btvacuumscan()</code>, which scans the <strong>entire index in physical order</strong> (every page except the metapage, including all leaf pages), checking every index entry&rsquo;s TID against the dead-TID store. Matching entries are removed. (See <code>btvacuumscan()</code> in <code>src/backend/access/nbtree/nbtree.c</code>, which processes each page with <code>btvacuumpage()</code>.)</li>
<li>Only <strong>after</strong> all indexes are cleaned does vacuum go back and clean the heap pages (mark <code>LP_UNUSED</code>, compact). This is handled by <code>lazy_vacuum_all_indexes()</code> followed by the heap cleanup phase in <a href="https://github.com/postgres/postgres/blob/REL_18_STABLE/src/backend/access/heap/vacuumlazy.c" target="_blank" rel="noopener noreferrer"><code>src/backend/access/heap/vacuumlazy.c</code></a>.</li>
</ol>
<p>The order of operations is important: index entries must be removed <strong>before</strong> their heap tuple slots are recycled, otherwise an index scan could follow a pointer to a slot that now holds a different, unrelated tuple, returning incorrect results.</p>
<h3 id="why-index-vacuum-is-expensive">Why Index Vacuum Is Expensive<a class="anchor-link" id="why-index-vacuum-is-expensive"></a></h3>
<p>The PostgreSQL documentation for the <a href="https://www.postgresql.org/docs/18/index-functions.html" target="_blank" rel="noopener noreferrer"><code>ambulkdelete</code> interface</a> states:</p>
<blockquote>
<p><em>This is a &ldquo;bulk delete&rdquo; operation that is intended to be implemented by <strong>scanning the whole index</strong> and checking each entry to see if it should be deleted.</em></p>
</blockquote>
<p>There is <strong>no partial index scan optimization</strong>. The design requires scanning the index completely. The dead TIDs are sorted by heap location, but index entries are ordered by key value, so there is no way to locate only the affected index pages without scanning all leaf pages.</p>
<p>Consequences:</p>
<ul>
<li>Each index is completely scanned for every vacuum cycle. For a table with 5 indexes and 100GB of index data, vacuum reads 500GB of index pages each full pass.</li>
<li>If the space for dead-TID is too small and the heap scan must pause mid-way, <code>ambulkdelete</code> is called <strong>multiple times</strong>, once per batch of dead TIDs. The documentation states: &ldquo;Because of limited <code>maintenance_work_mem</code>, <code>ambulkdelete</code> might need to be called more than once when many tuples are to be deleted.&rdquo; Each call performs a full index scan. With a 100GB table, 64MB of work memory, and 5 indexes, this can result in dozens of full index scans. (PG17&rsquo;s <code>TidStore</code> packs far more dead TIDs into the same memory, so this multi-pass case is much rarer than it was in previous versions.)</li>
<li>This is why increasing <code>autovacuum_work_mem</code> (or <code>maintenance_work_mem</code>) for vacuum-heavy workloads can be required. If the autovacuum operation is written into the log (<code>log_autovacuum_min_duration</code>), look for <code>index scans</code>.</li>
</ul>
<h3 id="index-cleanup-optimizations">Index Cleanup Optimizations<a class="anchor-link" id="index-cleanup-optimizations"></a></h3>
<ul>
<li>Bypass optimization (near-zero dead tuples): when 2% or fewer of the table&rsquo;s pages contain <code>LP_DEAD</code> items and the accumulated dead-TID storage stays under 32MB, vacuum enters bypass mode: it skips both index cleanup and the second heap-vacuuming pass, avoiding a full index scan as the benefit is reduced. This avoids the jump between &ldquo;zero dead tuples is instant&rdquo; and &ldquo;one dead tuple requires multiple full index scans&rdquo;. (See <code>BYPASS_THRESHOLD_PAGES</code> in <code>vacuumlazy.c</code>.)</li>
<li><code>INDEX_CLEANUP</code> parameter: <code>AUTO</code> (default) allows the bypass optimization; <code>OFF</code> forces vacuum to always skip index vacuuming (accepting index bloat); <code>ON</code> forces full index vacuuming every time. The <code>OFF</code> setting is useful for emergency situations where you need vacuum to advance <code>relfrozenxid</code> quickly. (See <a href="https://www.postgresql.org/docs/18/sql-vacuum.html" target="_blank" rel="noopener noreferrer">VACUUM documentation</a>.)</li>
<li>B-tree &ldquo;page deletion&rdquo;: when a B-tree leaf page becomes empty after vacuum removes all its entries, the page is marked as deleted and can be recycled. The file does not reduce its size, but the pages can be reused later.</li>
<li>Simple B-tree tuple deletion: when a query visits a dead tuple via an index scan, it can mark that pointer as dead in the index itself. If, at a later time, more space is needed in that page, instead of performing a split, the index entries pointing to dead tuples can be removed to make room for the new entry.</li>
<li>Bottom-up deletion (PG14+): B-tree indexes can proactively remove known-dead entries during page splits, reducing the work left for vacuum.</li>
</ul>
<h2 id="concurrency-vacuum-vs-active-backends">Concurrency: Vacuum vs. Active Backends<a class="anchor-link" id="concurrency-vacuum-vs-active-backends"></a></h2>
<p>Vacuum runs concurrently with normal database operations. It does <strong>not</strong> lock the table exclusively (it takes a <code>ShareUpdateExclusiveLock</code>, which conflicts only with other vacuums, <code>ALTER TABLE</code>, and certain <code>CREATE INDEX</code> operations).</p>
<h3 id="page-level-locking">Page-Level Locking<a class="anchor-link" id="page-level-locking"></a></h3>
<p>When vacuum needs to read or modify a heap page it uses the common shared buffer access locks:</p>
<ol>
<li>For reading (AKA identifying dead tuples), vacuum acquires a shared <strong>buffer content lock</strong> on the shared buffer.</li>
<li>For pruning and freezing (AKA removing dead tuples, setting vm flags), vacuum requires a <strong>buffer cleanup lock</strong>. This is an exclusive lock (no other backend can hold a lock on the buffer). In a non-aggressive vacuum, if the cleanup lock cannot be obtained immediately (another transaction has a shared lock for example), vacuum <strong>skips pruning/freezing on that page</strong> and moves on. An aggressive (anti-wraparound) vacuum will wait for the lock instead.</li>
<li>These locks are held only for the duration of the in-memory page operation and should be very fast. They do <strong>not</strong> block concurrent <code>SELECT</code> or <code>DML</code> on other pages.</li>
</ol>
<h3 id="what-happens-when-a-backend-reads-a-page-being-vacuumed">What Happens When a Backend Reads a Page Being Vacuumed<a class="anchor-link" id="what-happens-when-a-backend-reads-a-page-being-vacuumed"></a></h3>
<ul>
<li>If vacuum is <strong>currently modifying</strong> the page (holding the cleanup lock): the backend waits until vacuum releases the lock, then reads the page in its post-vacuum state. The backend sees only live tuples. The dead ones have just been removed. This is safe because the dead tuples were invisible to the backend&rsquo;s snapshot anyway.</li>
<li>If vacuum <strong>skipped</strong> the page (could not get the cleanup lock): dead tuples remain on the page. They are invisible to backends via MVCC visibility checks and will be cleaned up in a future vacuum cycle.</li>
<li>If vacuum has <strong>not yet reached</strong> the page: the backend reads normally. Dead tuples are still present but invisible to the backend&rsquo;s MVCC snapshot. They are skipped during visibility checks.</li>
</ul>
<h3 id="what-happens-when-a-backend-writes-while-vacuum-runs">What Happens When a Backend Writes While Vacuum Runs<a class="anchor-link" id="what-happens-when-a-backend-writes-while-vacuum-runs"></a></h3>
<ul>
<li>INSERT into a vacuumed page: vacuum freed space, the FSM knows about it, the inserter uses that space. No conflict.</li>
<li>UPDATE/DELETE on the same table: concurrent DML does not conflict with vacuum&rsquo;s <code>ShareUpdateExclusiveLock</code>. If a backend deletes/updates a tuple on a page vacuum hasn&rsquo;t reached yet, vacuum will find and clean it (if committed by then). If the tuple is on a page vacuum already passed, it will be caught by the next vacuum cycle.</li>
<li>UPDATE/DELETE on a page vacuum is currently processing: the buffer lock serializes access. If vacuum removes dead tuples and the backend then updates a live tuple on the same page, there&rsquo;s no conflict because they operate on different tuple slots.</li>
</ul>
<h3 id="index-scan-during-index-cleanup">Index Scan During Index Cleanup<a class="anchor-link" id="index-scan-during-index-cleanup"></a></h3>
<p>While vacuum scans an index to remove dead entries, concurrent index scans by backends can proceed normally. B-tree indexes use a <strong>pin-based</strong> protocol that avoids vacuum deleting a page that any backend has pinned. Specifically, vacuum marks pages as half-dead first, and only recycles them when no backend holds a pin. This ensures index scans never follow a pointer to a recycled page.</p>
<h2 id="threshold-formula">Threshold Formula<a class="anchor-link" id="threshold-formula"></a></h2>
<p>A table becomes eligible for autovacuum when its dead-tuple count crosses a threshold. As of PG18 the calculation is limited by <code>autovacuum_vacuum_max_threshold</code>:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">text</span><button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-0">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">vacuum_threshold = Min(
</span></span><span class="line"><span class="cl"> autovacuum_vacuum_max_threshold,
</span></span><span class="line"><span class="cl"> autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor * reltuples
</span></span><span class="line"><span class="cl">)</span></span></code></pre>
</div>
</div>
</div>
<p>Defaults:</p>
<ul>
<li><code>autovacuum_vacuum_threshold = 50</code></li>
<li><code>autovacuum_vacuum_scale_factor = 0.2</code></li>
<li><code>autovacuum_vacuum_max_threshold = 100,000,000</code> (<strong>new in PG18</strong>).</li>
</ul>
<p><code>autovacuum_vacuum_max_threshold</code> is used to avoid massive tables requiring a huge number of dead tuples before firing vacuum.</p>
<h3 id="insert-triggered-vacuum-pg13">Insert-triggered vacuum (PG13+)<a class="anchor-link" id="insert-triggered-vacuum-pg13"></a></h3>
<p>A table also becomes eligible based on inserts alone. Since PG18 the scale-factor term is multiplied by the <strong>unfrozen fraction</strong> of the table:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">text</span><button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-1">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">vacuum_insert_threshold =
</span></span><span class="line"><span class="cl"> autovacuum_vacuum_insert_threshold
</span></span><span class="line"><span class="cl"> + autovacuum_vacuum_insert_scale_factor * reltuples * (1 - relallfrozen / relpages)</span></span></code></pre>
</div>
</div>
</div>
<p>Defaults:</p>
<ul>
<li><code>autovacuum_vacuum_insert_threshold = 1000</code></li>
<li><code>autovacuum_vacuum_insert_scale_factor = 0.2</code></li>
</ul>
<p>The <code>(1 - relallfrozen / relpages)</code> is used to avoid time between runs constantly growing for tables that are mostly inserted. In previous versions, as the table grows the number of inserted rows required to trigger a vacuum used to grow also. With this optimization, the number of inserts required to trigger vacuum tends to be more constant.</p>
<h3 id="analyze-trigger">ANALYZE trigger<a class="anchor-link" id="analyze-trigger"></a></h3>
<p>The following formula applies to determine if ANALYZE is required:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">text</span><button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-2">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">changed_tuples &gt; analyze_threshold + analyze_scale_factor * reltuples</span></span></code></pre>
</div>
</div>
</div>
<p>Defaults: <code>autovacuum_analyze_threshold = 50</code>, <code>autovacuum_analyze_scale_factor = 0.1</code>.</p>
<p>Per-table overrides via <code>ALTER TABLE ... SET (autovacuum_vacuum_scale_factor = ...)</code> that take precedence over default configuration.</p>
<h2 id="cost-based-vacuum-throttling">Cost-Based Vacuum Throttling<a class="anchor-link" id="cost-based-vacuum-throttling"></a></h2>
<p>The cost-based mechanism is linked directly to the page-level operations described above. Every time vacuum touches a page, it incurs a cost depending on what happened:</p>
<p>Vacuum I/O is throttled via a cost/delay mechanism shared with manual <code>VACUUM</code>:</p>
<ul>
<li><code>vacuum_cost_page_hit</code> = 1, page already in shared buffers (cheap: no I/O, only CPU to inspect tuples)</li>
<li><code>vacuum_cost_page_miss</code> = 10 (PG17 and earlier; <strong>changed to 2 in PG18</strong>), page read from OS into shared buffers (may still be in OS page cache, so not necessarily a physical disk read)</li>
<li><code>vacuum_cost_page_dirty</code> = 20, vacuum modified the page (removed dead tuples, compacted it). This is the most expensive because it generates a dirty buffer that must eventually be written to disk by the background writer/checkpointer</li>
</ul>
<p>These costs are <strong>additive per page</strong>. On PG17 (miss = 10): a page read from disk and then modified costs <strong>30</strong>. On PG18 (miss = 2): the same scenario costs <strong>22</strong>. A page already in shared buffers (hit = 1) that gets modified costs <strong>21</strong> on both versions.</p>
<p>The cost limit is <strong>shared across all running autovacuum workers</strong>. If 3 workers are active, each effectively gets <code>200 / 3</code> or around <code>66</code> cost budget per cycle. This means adding more workers doesn&rsquo;t linearly increase I/O as all workers get their cost limit reduced. The global limit is <code>autovacuum_vacuum_cost_limit</code>, that by default is -1, meaning it inherits <code>vacuum_cost_limit</code>, which by default is 200.</p>
<p>The workers accumulate cost points as operations happen. When, for a specific worker, the accumulated total reaches its assigned limit, the worker will sleeps for <code>autovacuum_vacuum_cost_delay</code> (default 2ms).</p>
<h3 id="example">Example<a class="anchor-link" id="example"></a></h3>
<p>With defaults (limit=200, delay=2ms), one worker on <strong>PG17</strong> (miss=10):</p>
<ul>
<li>If all pages are a miss + dirty write (cost 30 each): 200/30 ~ <strong>6 pages</strong>, then sleep 2ms, giving ~3,000 pages/sec.</li>
<li>If every page is already in shared buffers and gets modified (hit + dirty = 21): 200/21 ~ <strong>9 pages</strong>, then sleep 2ms, giving ~4,500 pages/sec.</li>
<li>If all pages are a shared-buffer hit with no modifications (cost 1 each): 200 pages, then sleep 2ms, giving ~100,000 pages/sec.</li>
</ul>
<p>On <strong>PG18</strong> (miss=2), miss + dirty reduces the cost to 22 per page, so throughput for cold pages rises to 200/22 ~ 9 pages per cycle.</p>
<p>If we have 3 workers sharing the limit, then each gets 66 cost/cycle, so throughput per worker drops proportionally.</p>
<p>For large this default is often <strong>too conservative</strong>. Common tuning: raise <code>autovacuum_vacuum_cost_limit</code> to 1000-2000 and/or reduce <code>cost_delay</code> to 0 on critical tables.</p>
<p>The manual <code>VACUUM</code> parameter <code>vacuum_cost_delay</code> defaults to 0 (no throttling). Autovacuum workers use <code>autovacuum_vacuum_cost_delay</code>, which has the default value of 2ms since PG12 (earlier versions defaulted to 20ms). Per-table storage parameters <code>autovacuum_vacuum_cost_delay</code> / <code>autovacuum_vacuum_cost_limit</code> override the globals for that specific table. This a way to tune the impact of high-churn tables on the shared cost.</p>
<h2 id="what-drives-vacuum-cost">What Drives Vacuum Cost<a class="anchor-link" id="what-drives-vacuum-cost"></a></h2>
<p>As we&rsquo;ve seen, autovacuum is triggered by the number of dead or inserted tuples. But is the real cost driven by the number of dead tuples, or by the number of pages it has to visit and clean?.</p>
<p>We designed a benchmark to try to discover which is the real cost driver for autovacuum operations.</p>
<h3 id="the-question">The question<a class="anchor-link" id="the-question"></a></h3>
<p>We have two hypotheses that we want to analyze:</p>
<ol>
<li>Whether autovacuum cost is driven by the <strong>count of dead tuples</strong> or by the <strong>number of heap pages</strong> those dead tuples are spread across.</li>
<li>How the <strong>number of indexes</strong> amplifies that cost.</li>
</ol>
<h3 id="the-table-and-the-key-variable">The table and the key variable<a class="anchor-link" id="the-table-and-the-key-variable"></a></h3>
<p>We will use a single table for every run of the benchmark. We will drop and recreate the table each time:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">sql</span><button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-3">
<div class="highlight">
<pre class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">bench_table</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="nb">INTEGER</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span><span class="w"> </span><span class="c1">-- sequential 1 .. 10,000,000
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w"> </span><span class="n">val</span><span class="w"> </span><span class="nb">INTEGER</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="k">DEFAULT</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">padding</span><span class="w"> </span><span class="nb">TEXT</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="c1">-- repeat('x', 96)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">);</span></span></span></code></pre>
</div>
</div>
</div>
<p>The padding column fixes the row width at 128 bytes, giving around <strong>58 rows per 8 KB page</strong>. For 10M rows we will have <strong>172,414 heap pages</strong> or 1.3 GB. We fill the table, then run <code>VACUUM FREEZE</code> so every page starts <strong>all-visible and all-frozen</strong> to have a clean baseline. Then we create 0 to 5 <strong>redundant B-tree indexes</strong>, all on <code>id</code>. Each index is a separate physical structure that vacuum must scan in full.</p>
<p>The independent variable here is <em>the distribution of dead tuples</em>. We use two strategies delete the <strong>same number of rows</strong>. They differ only in which pages are touched:</p>
<table>
<thead>
<tr>
<th>Strategy</th>
<th>DELETE predicate (10%)</th>
<th>Pages dirtied</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>compact</strong></td>
<td><code>WHERE id &lt;= 1,000,000</code></td>
<td>first ~10% of pages (low ids = first physical pages)</td>
</tr>
<tr>
<td><strong>spread</strong></td>
<td><code>WHERE id % 10 = 0</code></td>
<td>100% of pages (every page loses between 5 and 6 rows of its 58 rows)</td>
</tr>
</tbody>
</table>
<p>For this benchmark, we use <code>DELETE</code> rather than <code>UPDATE</code> so no new tuple versions are created. The table does not grow and no index entries are added (no leaf page splits).</p>
<h3 id="the-test-matrix">The test matrix<a class="anchor-link" id="the-test-matrix"></a></h3>
<p>We have 6 index counts (0-5), multiplied by 3 dead-tuple percentages (10/25/50%) and 2 distributions gives us 36 combinations. We repeated each combination 10 times for a total of 360 runs.</p>
<h3 id="how-a-single-run-is-measured">How a single run is measured<a class="anchor-link" id="how-a-single-run-is-measured"></a></h3>
<ol>
<li>Recreate the table, fill it with data, <code>VACUUM FREEZE</code>, build the required indexes for the test (autovacuum disabled on the table throughout setup).</li>
<li><code>DELETE</code> to generate the dead tuples for this combination.</li>
<li>Pre-test <code>CHECKPOINT</code>: flush the buffers dirtied during setup, so the post-test checkpoint will only account for pages autovacuum makes dirty.</li>
<li>Reset the shared I/O counters: <code>pg_stat_reset_shared('io' | 'bgwriter' | 'checkpointer')</code>. Note that we do not call <code>pg_stat_reset()</code>, which would zero <code>n_dead_tup</code> and prevent autovacuum from triggering.</li>
<li>Record the start time, then enable autovacuum on the table with parameters that should trigger autovacuum (<code>autovacuum_vacuum_threshold = 1</code>, <code>autovacuum_vacuum_scale_factor = 0</code>). As <code>autovacuum_naptime</code> is 1s, the launcher should pick the table up approximately within a second.</li>
<li>Poll every 0.5 s until vacuum is done: <code>last_autovacuum</code> is after start_time<code>and</code>n_dead_tup = 0`.</li>
<li>Stop the clock, force a post-test checkpoint, and collect metrics.</li>
</ol>
<h3 id="what-is-captured-and-from-where">What is captured, and from where<a class="anchor-link" id="what-is-captured-and-from-where"></a></h3>
<table>
<thead>
<tr>
<th>Signal</th>
<th>Source</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td>Wall-clock <code>duration_s</code></td>
<td><code>clock_gettime</code> (monotonic)</td>
<td>approximate time (vacuum+nap)</td>
</tr>
<tr>
<td>Autovacuum-worker reads/hits/writes</td>
<td><code>pg_stat_io</code>, filtered to <code>backend_type = 'autovacuum worker'</code></td>
<td>isolates the worker from every other process (PG18)</td>
</tr>
<tr>
<td>Heap vs index blocks</td>
<td><code>pg_statio_user_tables</code> (before/after diff)</td>
<td>specific table data</td>
</tr>
<tr>
<td>Write breakdown</td>
<td><code>pg_stat_io</code> per backend + <code>pg_stat_checkpointer</code></td>
<td>who wrote the dirty pages</td>
</tr>
<tr>
<td>Dirty-page count / completion</td>
<td><code>pg_visibility_map_summary</code> (from <code>pg_visibility</code>)</td>
<td></td>
</tr>
</tbody>
</table>
<p>I/O is recorded two ways: <strong>operation counts</strong> (<code>reads</code>/<code>writes</code>, which can be multi-block) and <strong>byte-derived page counts</strong> (<code>read_bytes</code>/<code>write_bytes</code> &divide; 8192, exact regardless of multi-block coalescing). We decided to look at the byte-derived counts. Each combination&rsquo;s 10 iterations are aggregated as a <strong>median with an interquartile (Q1-Q3) band</strong>.</p>
<h3 id="results">Results<a class="anchor-link" id="results"></a></h3>
<p>This is the test environment we used:</p>
<ul>
<li>PostgreSQL: 18.4 (PGDG, <code>pg_visibility</code> contrib)</li>
<li>OS / host: Ubuntu 24.04 LTS, x86_64, 4 dedicated vCPU / 15 GiB RAM, SSD-backed</li>
<li>Execution: benchmark runs locally on the DB host over the Unix socket (no network in the timing path)</li>
<li>Key GUCs:
<ul>
<li><code>shared_buffers = 4GB</code></li>
<li><code>maintenance_work_mem = 1GB</code></li>
<li><code>work_mem = 64MB</code></li>
<li><code>autovacuum_naptime = 1s</code></li>
<li><code>autovacuum_vacuum_cost_delay = 2ms</code></li>
<li><code>autovacuum_vacuum_cost_limit = 200</code></li>
<li><code>vacuum_cost_page_miss = 2</code></li>
<li><code>checkpoint_timeout = 15min</code></li>
<li><code>max_wal_size = 4GB</code></li>
<li><code>full_page_writes = on</code></li>
<li><code>track_io_timing = on</code></li>
</ul>
</li>
<li>Per-table triggers:
<ul>
<li><code>autovacuum_vacuum_threshold = 1</code></li>
<li><code>autovacuum_vacuum_scale_factor = 0</code></li>
</ul>
</li>
<li>Workload:
<ul>
<li>Table with 10,000,000 rows (172,414 heap pages, ~1.3 GB)</li>
<li>2.4 GB working set with 5 indexes (around 225 MB each), fits completely in <code>shared_buffers</code></li>
</ul>
</li>
<li>Sampling: 36 combinations x 10 iterations = 360 runs</li>
</ul>
<p>The whole 2.4 GB working set (heap plus five 225 MB indexes) is resident in <code>shared_buffers</code>. The <code>maintenance_work_mem = 1 GB</code> keeps index cleanup single-pass. Autovacuum throttling is at the default (<code>cost_delay = 2ms</code>). Durations below are 10-iteration medians.</p>
<p>Hypothesis 1: it&rsquo;s pages, not tuples. At 0 indexes:</p>
<table>
<thead>
<tr>
<th>dead tuples</th>
<th style="text-align: right">compact</th>
<th style="text-align: right">spread</th>
<th style="text-align: right">spread / compact</th>
</tr>
</thead>
<tbody>
<tr>
<td>10% (1.0M)</td>
<td style="text-align: right">5.0 s</td>
<td style="text-align: right">43.6 s</td>
<td style="text-align: right">8.7x</td>
</tr>
<tr>
<td>25% (2.5M)</td>
<td style="text-align: right">11.5 s</td>
<td style="text-align: right">43.6 s</td>
<td style="text-align: right">3.8x</td>
</tr>
<tr>
<td>50% (5.0M)</td>
<td style="text-align: right">21.8 s</td>
<td style="text-align: right">43.4 s</td>
<td style="text-align: right">2.0x</td>
</tr>
</tbody>
</table>
<p>First thing we see is that the <strong>spread column is almost constant</strong> (43.6, 43.6, 43.4 s) as the dead-tuple count goes from 1M to 5M, because in every case 100% of pages are dirtied. This means that pages visited is the cost driver. Meanwhile the compact column scales because, for compact, more dead tuples means more pages become dirty. When we have 50% dead tuples, the cost is half the spread.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/pep_autovacuum_duration_compact_vs_spread.png" alt="Autovacuum duration, compact vs spread, one panel per dead-tuple percentage. Spread (dashed) sits far above compact (solid) and is nearly flat across 10/25/50%."></figure>
</p>
<p>The following chart plots every run by <em>dirty-page percentage</em> rather than dead-tuple count.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/pep_autovacuum_dirty_pages_vs_duration.png" alt="Scatter of autovacuum duration against percentage of pages dirty before vacuum; points rise with dirty-page %, colored by index count."></figure>
</p>
<p>For the second hypothesis, we see that indexes amplify the cost. Each redundant index adds a near-constant increment (compact 50%): 21.8, 28.1, 32.6, 37.6, 42.3, 47.4 s. Around 5s per index. The number of dead pages also adds some cost, but if the number of dead pages is reduced, then the impact of indexes is lower (compact 10%)</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/pep_autovacuum_duration_by_indexes.png" alt="Autovacuum duration versus number of indexes, compact and spread panels; each line rises roughly linearly with index count."></figure>
</p>
<p>The I/O data shows some <strong>distribution-specific</strong> behaviors. For spread 50%, heap blocks accessed hold at <strong>547,323</strong> across 1-5 indexes (the heap is scanned once, guided by the VM) while index blocks grow <strong>+27,422 per index</strong>. Each <code>ambulkdelete</code> is a full leaf scan. Compact 50% behaves differently: the heap io is lower (<strong>288,699</strong>, since fewer pages have dead tuples) but index blocks climb <strong>4x faster (~+109,533 per index)</strong>. Deleting contiguous id ranges empties whole B-tree leaf pages, adding page-deletion and recycling work (B-trees recycle fully-empty pages rather than merging partially-filled ones) on top of the scan. One thing worth noting is that heap access is <strong>not</strong> flat from 0 to 1 index, it jumps (spread 374,903 to 547,323) because index cleanup forces vacuum&rsquo;s second heap pass. After that, it is flat only across 1-5 indexes.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/pep_autovacuum_heap_vs_index_io.png" alt="Stacked heap-versus-index blocks accessed by index count; heap roughly constant across 1&ndash;5 indexes while index I/O grows linearly, more steeply for compact than spread."></figure>
</p>
<p>The write breakdown tells us that who writes vacuum&rsquo;s dirtied pages is not fixed. With zero or one index, the autovacuum worker writes almost nothing: it dirties heap buffers and leaves them for the checkpointer, which flushes all of them in those cases. But as indexes are added, the <strong>worker&rsquo;s own writes increase</strong>: spread 50% goes 0, 27k, 54k, 82k, 109k, 136k pages written by the worker for 0-5 indexes (compact 50%: 0, 0, 14k, 27k, 41k, 55k). This is the effect of the <strong>buffer ring</strong> (see <em>Limiting Cache Impact</em> above): once index cleanup dirties more pages than the 2 MB ring can hold, the worker has to write and evict them itself rather than defer to the checkpointer. So the write cost shifts from the checkpointer toward the worker as index count grows, a direct consequence of the cache-protecting ring.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/pep_autovacuum_write_breakdown.png" alt="Write breakdown by process; at low index counts the checkpointer does nearly all writes, but the autovacuum worker&rsquo;s own writes grow with index count."></figure>
</p>
<p>Finally we have a heat map of the durations across all 36 combinations:</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/pep_autovacuum_duration_heatmap.png" alt="Heatmap of autovacuum duration for every distribution/dead-percentage/index-count combination."></figure>
</p>
<p>So our conclusion is, as expected, that <strong>dirty pages is the main cost driver for autovacuum, followed by the number of indexes</strong>. And that we may have the same number of dead rows with completely different autovacuum costs.</p>
<h3 id="what-this-benchmark-does-and-does-not-show">What this benchmark does and does not show<a class="anchor-link" id="what-this-benchmark-does-and-does-not-show"></a></h3>
<p>The result is clear, but getting a clear result using a synthetic workload should be read with care. Our benchmark intentionally avoids complexity. And complexity is what makes production vacuum hard.</p>
<ul>
<li>We built a <code>VACUUM</code> benchmark wearing autovacuum&rsquo;s clothes. With one table, <code>threshold = 1</code>, <code>scale_factor = 0</code>, and <code>naptime = 1s</code>, we measure the isolated work of a single worker on one table. It says nothing about the parts that are specific of autovacuum: launcher table-selection, <code>autovacuum_max_workers</code> contention, or the cost limit shared across workers. The scheduling dynamics are often critical, and they are absent here.</li>
<li>The workload we use in the benchmark, as usual for a benchmark, is synthetic. Dead tuples come from a single bulk <code>DELETE</code> on an idle table. There is no concurrency. There are no long-running transaction holding back <code>OldestXmin</code>. Real vacuum routinely skips pages it can&rsquo;t get a cleanup lock on (see <em>Concurrency</em>). In our case, this never happens, which is why every run cleans to 100% all-visible. The benchmark measures vacuum on an idealized table, not production churn.</li>
<li>Everything fits in memory, so this is not an I/O-bound situation. With the whole table in <code>shared_buffers</code>, reads are mostly buffer hits, and writes are largely deferred to the checkpointer (the worker itself writes little except index pages at higher index counts). The &ldquo;cost&rdquo; being measured is page visits and CPU, plus deferred checkpoint writes. The vacuums that are I/O-bound due to scans of heaps and indexes that don&rsquo;t fit in cache, are painful. This case is excluded from the benchmark and we may say that the conclusions are about <em>logical</em> work, rather than IO bound operations. We consider that IO bound operations could show greater differences, but we did not test them.</li>
<li>Redundant identical indexes don&rsquo;t generalize. Five B-tree indexes on the integer column is a trick used to make index work scale linearly. Real indexes differ in width, key type, correlation, bloat, fill factor, and bottom-up-deletion behavior. The measured slope (5 s per index; 27,422 index blocks per index for spread, but 109,533 for compact) is specific to this shape (even the slope is distribution-dependent) and can not be directly extrapolated to wide, composite, or text indexes.</li>
<li>Freezing was excluded. The <code>VACUUM FREEZE</code> baseline ignored freezing. Anti-wraparound / aggressive vacuums, which must visit every not-all-frozen page and are often the most disruptive events in production, are not part of this benchmark. Besides, the bypass optimization and multi-pass <code>ambulkdelete</code> (under <code>maintenance_work_mem</code> pressure) is avoided, and this makes vacuum runtime nonlinear and hard to predict in production.</li>
</ul>
<p>Our findings are valid: vacuum cost is driven by dirty pages, not by dead-tuple count, and the mechanism behind it is clear. But the benchmark used a single table that fits entirely in memory, recently frozen, and ran with no concurrent activity. Under those conditions, both runtime and the page-visit counts grew linearly with the number of dirty pages and the number of indexes. It is not clear if these results can be extrapolated to a busy, larger-than-RAM production system. This is left for a future exercise.</p>
<h2 id="references">References<a class="anchor-link" id="references"></a></h2>
<h3 id="postgresql-documentation">PostgreSQL Documentation<a class="anchor-link" id="postgresql-documentation"></a></h3>
<ul>
<li><a href="https://www.postgresql.org/docs/18/index-functions.html" target="_blank" rel="noopener noreferrer">Index Access Method Functions</a>. Defines the <code>ambulkdelete</code> and <code>amvacuumcleanup</code> interfaces; documents that bulk delete is &ldquo;intended to be implemented by scanning the whole index&rdquo;</li>
<li><a href="https://www.postgresql.org/docs/18/sql-vacuum.html" target="_blank" rel="noopener noreferrer">VACUUM SQL Command</a>. <code>INDEX_CLEANUP</code> parameter (<code>AUTO</code>/<code>ON</code>/<code>OFF</code>) and <code>PARALLEL</code> option for index vacuum</li>
<li><a href="https://www.postgresql.org/docs/18/btree.html#BTREE-IMPLEMENTATION" target="_blank" rel="noopener noreferrer">B-Tree Implementation</a>. B-tree structure, deduplication, and bottom-up deletion (page-deletion and recycling mechanics are detailed in the <code>nbtree/README</code> listed under Source Code below)</li>
</ul>
<h3 id="postgresql-source-code">PostgreSQL Source Code<a class="anchor-link" id="postgresql-source-code"></a></h3>
<ul>
<li><a href="https://github.com/postgres/postgres/blob/REL_18_STABLE/src/backend/access/nbtree/nbtree.c" target="_blank" rel="noopener noreferrer"><code>src/backend/access/nbtree/nbtree.c</code></a>. B-tree vacuum implementation: <code>btbulkdelete()</code> then <code>btvacuumscan()</code> (physical-order scan of all index pages except the metapage), <code>btvacuumpage()</code> (per-page processing)</li>
<li><a href="https://github.com/postgres/postgres/blob/REL_18_STABLE/src/backend/access/heap/vacuumlazy.c" target="_blank" rel="noopener noreferrer"><code>src/backend/access/heap/vacuumlazy.c</code></a>. Main VACUUM implementation: heap scan, dead tuple collection, <code>lazy_vacuum_all_indexes()</code> (iterates all indexes calling <code>ambulkdelete</code>), bypass optimization (<code>BYPASS_THRESHOLD_PAGES = 0.02</code>)</li>
<li><a href="https://github.com/postgres/postgres/blob/REL_18_STABLE/src/include/access/nbtree.h" target="_blank" rel="noopener noreferrer"><code>src/include/access/nbtree.h</code></a>. B-tree data structures and constants</li>
<li><a href="https://github.com/postgres/postgres/blob/REL_18_STABLE/src/backend/access/nbtree/README" target="_blank" rel="noopener noreferrer"><code>src/backend/access/nbtree/README</code></a>. Design notes on B-tree page deletion, recycling, and the half-dead/pin-based deletion protocol</li>
</ul>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/07/01/postgresql-autovacuum-internals-benchmark/">PostgreSQL Autovacuum Internals and Benchmark</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Building a Modern Analytics Stack Around ClickHouse</title>
      <link rel="alternate" type="text/html" href="https://severalnines.com/blog/building-a-modern-analytics-stack-around-clickhouse/" />
      <id>https://severalnines.com/blog/building-a-modern-analytics-stack-around-clickhouse/</id>
      <updated>2026-07-01T10:35:21+00:00</updated>
      <author><name>Sebastian Insausti</name></author>
      <summary type="html"><![CDATA[<p>Historically, relational databases did double duty. The same PostgreSQL / MySQL instance that handled your application’s writes also answered your business questions. A well-indexed schema, a few GROUP BY reports, done. And that works, right up until it doesn’t: the reports get slower, the dashboards start eating the same I/O budget as the application; or, […]<br />
The post Building a Modern Analytics Stack Around ClickHouse appeared first on Severalnines.</p>
<p>The post <a rel="nofollow" href="https://severalnines.com/blog/building-a-modern-analytics-stack-around-clickhouse/">Building a Modern Analytics Stack Around ClickHouse</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Historically, relational databases did double duty. The same <a href="https://severalnines.com/clustercontrol/databases/postgresql">PostgreSQL</a> / <a href="https://severalnines.com/clustercontrol/databases/mysql">MySQL</a> instance that handled your application&rsquo;s writes also answered your business questions. A well-indexed schema, a few GROUP BY reports, done. And that works, right up until it doesn&rsquo;t: the reports get slower, the dashboards start eating the same I/O budget as the application; or, the sheer volume of events buries a row-oriented engine that was designed for point lookups, not scans over a billion rows.</p>
<p>The industry&rsquo;s answer has been polyglot persistence &mdash; pick the right engine for each job. Caching went to <a href="https://severalnines.com/clustercontrol/databases/redis">Redis</a>. Telemetry went to Prometheus. And analytics, increasingly, goes to a columnar OLAP engine, and among the open-source options, ClickHouse is the one that keeps coming up. This post covers where ClickHouse sits in a modern data stack, how it coexists with the relational systems you already run, and what actually changes for the team that has to keep it all healthy.</p>
<h2 class="wp-block-heading" id="h-clickhouse-primer">ClickHouse Primer<a class="anchor-link" id="clickhouse-primer"></a></h2>
<p>Introduced by Yandex, it&rsquo;s an open-source, column-oriented DBMS built for analytical workloads, not for managing transactional records. Its core characteristics center around data structure, processing architecture and ingestion model.</p>
<h3 class="wp-block-heading" id="h-columnar-storage">Columnar storage<a class="anchor-link" id="columnar-storage"></a></h3>
<p>A row store reads entire rows even when your query only touches two columns out of fifty. ClickHouse stores each column as its own compressed file on disk, so a query over event_date and revenue reads exactly those two columns and nothing else. Combine that with vectorized execution, processing column values in batches using SIMD instructions, and you get the headline numbers ClickHouse is known for: billions of rows per second scanned on ordinary hardware.</p>
<h3 class="wp-block-heading" id="h-distributed-processing">Distributed processing<a class="anchor-link" id="distributed-processing"></a></h3>
<p><a href="https://severalnines.com/blog/clickhouse-scaling-and-sharding-best-practices/">Scaling out works through sharding and replication</a>. A distributed table engine fans queries out across shards and merges the results, while a Keeper-based coordination layer, Apache ZooKeeper or ClickHouse Keeper, keeps replicas consistent. That said, don&rsquo;t reach for a cluster on day one. A single node with plenty of RAM and fast NVMe storage goes a surprisingly long way; bring in sharding when query latency or ingest volume actually demands it.</p>
<h3 class="wp-block-heading" id="h-real-time-ingestion">Real-time ingestion<a class="anchor-link" id="real-time-ingestion"></a></h3>
<p>This is where ClickHouse separates itself from the nightly-batch warehouse model: it can ingest millions of rows per second and make them queryable almost immediately. The MergeTree engine family writes incoming data as immutable parts on disk and merges them asynchronously in the background, conceptually similar to an LSM tree. Variants like ReplacingMergeTree and AggregatingMergeTree handle deduplication and pre-aggregation during those merges, without the lock contention you&rsquo;d fight in an MVCC row store.</p>
<h2 class="wp-block-heading" id="h-the-modern-analytics-stack-components">The Modern Analytics Stack: Components<a class="anchor-link" id="the-modern-analytics-stack-components"></a></h2>
<p>Before placing ClickHouse on the map, it helps to name the layers of the map itself. The diagram below is the reference architecture we&rsquo;ll use for the rest of the post.</p>
<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="660" src="https://severalnines.com/wp-content/uploads/2026/06/modern-clickhouse-data-platform-architecture-1024x660.png" alt="Comprehensive enterprise data platform diagram showing ingestion from multiple data sources through Apache Kafka into ClickHouse, serving BI tools and data science under a data governance layer." class="wp-image-44180"></figure>
<h3 class="wp-block-heading">Data ingestion and streaming<a class="anchor-link" id="data-ingestion-and-streaming"></a></h3>
<p>Data arrives through two channels. The first is Change Data Capture: tools like Debezium or PeerDB read your OLTP database&rsquo;s replication log, MySQL&rsquo;s binlog, PostgreSQL&rsquo;s logical replication, and publish row-level change events to Kafka. The second is direct instrumentation: clickstream events, API logs, telemetry, and IoT data that applications push straight to Kafka without touching a relational database at all.</p>
<h3 class="wp-block-heading">Storage and compute engine<a class="anchor-link" id="storage-and-compute-engine"></a></h3>
<p>ClickHouse sits in the middle as the analytical store. It&rsquo;s not a data lake; it manages its own storage rather than querying files in object storage, and it&rsquo;s not a cloud-only warehouse either; it runs on-prem just as happily. Think of it as the query engine closest to your data consumers: low latency, high concurrency, and able to absorb high-velocity inserts from Kafka or batch pipelines without falling behind.</p>
<h3 class="wp-block-heading">Visualization and BI layer<a class="anchor-link" id="visualization-and-bi-layer"></a></h3>
<p>ClickHouse speaks a MySQL-compatible wire protocol and exposes a native HTTP interface, so nearly any BI tool with a SQL data source can connect to it. Grafana, Apache Superset, Metabase, Tableau, Looker, DBeaver. In most cases, the setup is nothing more than a JDBC/ODBC driver or the HTTP endpoint URL.</p>
<h3 class="wp-block-heading">Governance, metadata, and security<a class="anchor-link" id="governance-metadata-and-security"></a></h3>
<p>A fast query engine alone doesn&rsquo;t make a production stack. Schema registries, e.g., Confluent Schema Registry, Apicurio, enforce contract compatibility on Kafka topics and data catalogs, e.g. DataHub, track lineage and ownership. On the ClickHouse side, you get TLS for client and inter-replica traffic, row-level access policies, and column-level access control.</p>
<h2 class="wp-block-heading">Database Type Comparison<a class="anchor-link" id="database-type-comparison"></a></h2>
<p>Before continuing, it&rsquo;s worth grounding all of this in a side-by-side comparison: a traditional OLTP store, a conventional OLAP warehouse, and ClickHouse.</p>
<figure class="wp-block-table">
<table class="has-fixed-layout">
<tbody>
<tr>
<td><strong>Characteristic</strong></td>
<td><strong>OLTP</strong></td>
<td><strong>Traditional OLAP</strong></td>
<td><strong>ClickHouse</strong></td>
</tr>
<tr>
<td>Primary workload</td>
<td>Transactional reads/writes, point lookups</td>
<td>Batch analytics, historical reporting</td>
<td>Real-time and historical analytics</td>
</tr>
<tr>
<td>Storage model</td>
<td>Row-oriented</td>
<td>Columnar</td>
<td>Columnar (MergeTree family)</td>
</tr>
<tr>
<td>Ingestion latency</td>
<td>Sub-millisecond per row</td>
<td>Minutes to hours (batch COPY/LOAD)</td>
<td>Sub-second (streaming inserts)</td>
</tr>
<tr>
<td>Query latency (analytical)</td>
<td>Seconds to minutes (degrades with row count)</td>
<td>Seconds to tens of seconds</td>
<td>Milliseconds to low seconds</td>
</tr>
<tr>
<td>Concurrent write/read isolation</td>
<td>Full MVCC / ACID</td>
<td>Limited; primarily append-only</td>
<td>Eventual consistency; no row-level locking</td>
</tr>
<tr>
<td>UPDATE / DELETE support</td>
<td>Native, row-level</td>
<td>Supported but costly</td>
<td>Supported via ALTER mutations (async, expensive)</td>
</tr>
<tr>
<td>Typical row scale</td>
<td>Millions&ndash;low billions</td>
<td>Billions&ndash;trillions (with partitioning)</td>
<td>Billions&ndash;trillions (single node to cluster)</td>
</tr>
<tr>
<td>Horizontal scaling</td>
<td>Read replicas; sharding is complex</td>
<td>Native MPP / auto-scaling</td>
<td>Distributed tables with sharding + replication</td>
</tr>
<tr>
<td>Compression ratio</td>
<td>1&ndash;2&times; (page-level compression)</td>
<td>3&ndash;8&times;</td>
<td>5&ndash;20&times; (per-column codec selection)</td>
</tr>
<tr>
<td>Joins</td>
<td>Full join support, optimised for FK lookups</td>
<td>Full join support</td>
<td>Supported; large-scale joins require careful schema design (denormalization preferred)</td>
</tr>
<tr>
<td>Full-text search</td>
<td>Basic LIKE / full-text indexes</td>
<td>Minimal</td>
<td>Bloom filters; not a replacement for dedicated search engines</td>
</tr>
<tr>
<td>Deployment options</td>
<td>Self-managed, RDS/Cloud SQL, etc.</td>
<td>Managed cloud only (typically)</td>
<td>Self-managed, ClickHouse Cloud, or local binary</td>
</tr>
<tr>
<td>Operational tooling</td>
<td>Mature: ClusterControl, Percona, pg_upgrade</td>
<td>Vendor-managed</td>
<td>Growing: ClusterControl, Altinity Operator for K8s</td>
</tr>
<tr>
<td>Best fit</td>
<td>Application state, financial records, user data</td>
<td>Compliance reporting, long-retention warehousing</td>
<td>Real-time dashboards, event analytics, observability</td>
</tr>
</tbody>
</table>
</figure>
<h2 class="wp-block-heading">How ClickHouse Co-exists with Other Databases<a class="anchor-link" id="how-clickhouse-co-exists-with-other-databases"></a></h2>
<p>ClickHouse is additive. Your OLTP database keeps doing what it does best; the work is in getting data from one to the other cleanly.</p>
<h3 class="wp-block-heading">OLTP stores &rarr; ETL/CDC &rarr; ClickHouse<a class="anchor-link" id="oltp-stores-%e2%86%92-etl-cdc-%e2%86%92-clickhouse"></a></h3>
<p>CDC-based replication is the standard pattern. MySQL or PostgreSQL remains the source of truth for transactional integrity. Debezium (or an equivalent) tails the transaction log and pushes change events into Kafka, and from there, ClickHouse picks them up, either through its native Kafka engine or through a pipeline built on something like Flink or dbt.</p>
<p>One design decision deserves real thought here: do you replicate raw CDC events, or pre-aggregated facts? Raw events keep every aggregation option open downstream, but they cost more storage, and you&rsquo;ll need to manage ReplacingMergeTree carefully to handle upserts. Pre-aggregating on its way in keeps ClickHouse lean and queries simple, but locks you into whatever aggregation schema you chose. There&rsquo;s no universally right answer; just be deliberate about it.</p>
<h3 class="wp-block-heading">Handling slow and fast lanes<a class="anchor-link" id="handling-slow-and-fast-lanes"></a></h3>
<p>Not all analytics data moves at the same speed. A typical e-commerce platform has both:</p>
<ul class="wp-block-list">
<li>Fast lane: clickstream events (page views, add-to-cart, checkout steps) arriving at 10,000 to 100,000 events per second through Kafka, ingested directly by ClickHouse and queryable within seconds.</li>
<li>Slow lane: order records replicated from MySQL through Debezium, a few hundred per minute. Volume is low enough that pipeline latency barely matters.</li>
</ul>
<p>The payoff of landing both in ClickHouse is that a single SQL query can join real-time funnel data against historical order revenue. Try that against the production MySQL instance, and you&rsquo;ll be waiting a while, and so will your application.</p>
<h3 class="wp-block-heading">Hybrid workloads and real-time dashboards<a class="anchor-link" id="hybrid-workloads-and-real-time-dashboards"></a></h3>
<p>The usual end state: ClickHouse takes all analytical queries, and the OLTP database keeps all transactional reads and writes. A dashboard showing orders in the last five minutes or conversion by funnel step runs entirely on ClickHouse, with data that&rsquo;s maybe 5&ndash;30 seconds behind the actual transactions. For almost any business analytics use case, that lag is a non-issue, and the queries come back orders of magnitude faster than the equivalent aggregation on MySQL would.</p>
<h2 class="wp-block-heading">Operational Implications for Support and Ops Teams<a class="anchor-link" id="operational-implications-for-support-and-ops-teams"></a></h2>
<p>Adding ClickHouse to a heterogeneous environment creates new responsibilities. Here&rsquo;s what actually changes for the team on call.</p>
<h3 class="wp-block-heading">Data pipelines, latency, and data freshness<a class="anchor-link" id="data-pipelines-latency-and-data-freshness"></a></h3>
<p>Once analytics moves to ClickHouse, queries no longer hit the authoritative source. Every dashboard now carries an implicit freshness guarantee, and that guarantee is only as good as the pipeline behind it. Define the SLA explicitly, &ldquo;metrics are at most 60 seconds stale&rdquo;, and write it down. Then monitor end-to-end lag, not just whether each component is up. If Kafka consumer lag creeps up because ClickHouse inserts are slowing down, your dashboards quietly go stale without a single error being thrown.</p>
<p>Worth watching:</p>
<ul class="wp-block-list">
<li>Kafka consumer group lag</li>
<li>ClickHouse insert latency and the asynchronous insert logs</li>
<li>Debezium connector status and CDC production rate</li>
<li>The time delta between the Debezium source timestamp and arrival in ClickHouse</li>
</ul>
<h3 class="wp-block-heading">Schema evolution, partitions, and materialized views<a class="anchor-link" id="schema-evolution-partitions-and-materialized-views"></a></h3>
<p>ClickHouse is forgiving about schema changes, with one exception. Adding, dropping, or renaming columns is fast and cheap because columnar storage means each column lives in its own files. Changing a column&rsquo;s type is the expensive one: it triggers a mutation that rewrites that column&rsquo;s data in every part. Plan type changes; don&rsquo;t sweat the rest.</p>
<p>Partitioning is a first-class operational lever. Partition by a date truncation, and old data can be dropped instantly with <code>DROP PARTITION</code>; retention policies become nearly free. Just don&rsquo;t make partitions too wide; if you partition by year instead of month, you lose the precision that makes the technique useful.</p>
<p>Materialized views deserve respect. In ClickHouse they&rsquo;re insert triggers: every insert into the source table fires the view and writes pre-aggregated results to a target table, incrementally and in real time, no manual REFRESH like PostgreSQL. Extremely useful for running aggregates, but a badly written view sits directly in the insert path, so it can drag down ingest throughput on the source table. Test them under load.</p>
<h3 class="wp-block-heading">Resource isolation and multi-tenant considerations<a class="anchor-link" id="resource-isolation-and-multi-tenant-considerations"></a></h3>
<p>Resource control works through user profiles and quotas: <a href="https://severalnines.com/blog/managing-clickhouse-resources-in-multi-tenant-environments/">max memory per query, concurrent queries per user, and CPU threads</a>. If multiple teams share one cluster, per-team profiles are what stop someone&rsquo;s unoptimized ad-hoc query from starving the dashboards everyone else depends on. There&rsquo;s no per-query isolation at the container or cgroup level, though. If you need hard isolation, the boundary is a separate instance, or, a separate service on ClickHouse Cloud. For most teams starting out, one instance with sensible profiles is plenty.</p>
<h2 class="wp-block-heading">Tools and Management in Multi-Database Operations<a class="anchor-link" id="tools-and-management-in-multi-database-operations"></a></h2>
<p>Run MySQL, PostgreSQL, and ClickHouse side by side, and you&rsquo;re suddenly maintaining three backup toolchains, three monitoring integrations, and three sets of alert rules, unless something unifies them. That&rsquo;s the operational case for <a href="https://severalnines.com/clustercontrol">ClusterControl</a>: one management plane across relational and analytical clusters.</p>
<h3 class="wp-block-heading">Why unified management matters in heterogeneous stacks<a class="anchor-link" id="why-unified-management-matters-in-heterogeneous-stacks"></a></h3>
<p>Every engine has its own backup format, replication model, and metrics vocabulary. MySQL backups mean binary logs and snapshot tooling; PostgreSQL means WAL archiving; ClickHouse has its own BACKUP commands. Run each from its own toolchain, and you can&rsquo;t answer a question as basic as &ldquo;is everything in my estate backed up and verified within the last 24 hours?&rdquo; without stitching together data from three places.</p>
<p>ClusterControl pulls that into one plane:</p>
<ul class="wp-block-list">
<li>Unified backup scheduling and verification for MySQL, <a href="https://severalnines.com/clustercontrol/databases/mariadb">MariaDB</a>, PostgreSQL, ClickHouse, and more</li>
<li>Centralized metrics collection with dashboards per database type</li>
<li>Alert routing to Slack, PagerDuty, email, etc, regardless of which engine fired the alert</li>
<li>Topology visualization, health, lag, and failover state across every cluster</li>
</ul>
<h3 class="wp-block-heading">Implementation checklist for operational readiness<a class="anchor-link" id="implementation-checklist-for-operational-readiness"></a></h3>
<ul class="wp-block-list">
<li>Document data freshness SLAs for every ClickHouse table fed by a pipeline</li>
<li>Monitor Kafka consumer lag, e.g. Prometheus or Kafka Exporter</li>
<li>Set per-profile memory and execution-time limits in ClickHouse</li>
<li>Automate partition retention with DROP PARTITION jobs</li>
<li>Test ClickHouse backup and restore, including materialized view recovery</li>
<li>Write down schema change procedures and their cost implications</li>
<li>Track CDC pipeline health and binlog positions</li>
<li>Route ClickHouse alerts through the same channels as everything else</li>
<li>Write runbooks for the predictable failures: Kafka leader elections, CDC connector restarts, and merge backlogs</li>
</ul>
<h2 class="wp-block-heading">Ingestion Example<a class="anchor-link" id="ingestion-example"></a></h2>
<p>The following examples show two common patterns for getting data into ClickHouse: reading directly from a Kafka topic using the Kafka table engine, and bulk-loading from a CSV file.</p>
<h3 class="wp-block-heading">Pattern 1: Real-time ingestion from Kafka<a class="anchor-link" id="pattern-1-real-time-ingestion-from-kafka"></a></h3>
<p>ClickHouse&rsquo;s Kafka engine acts as a consumer of a Kafka topic. You create a Kafka engine table that describes the topic connection, and then a materialized view that pipes rows from that engine table into a MergeTree storage table. The Kafka engine table itself does not store data; it is only a consumer interface.</p>
<h4 class="wp-block-heading">ClickHouse SQL &ndash; Kafka engine and materialized view</h4>
<p><strong>1. Create the destination storage table (MergeTree)</strong></p>
<pre class="wp-block-code"><code>CREATE TABLE events.pageviews
(
&nbsp;&nbsp;&nbsp;&nbsp;event_time &nbsp; DateTime,
&nbsp;&nbsp;&nbsp;&nbsp;session_id &nbsp; UUID,
&nbsp;&nbsp;&nbsp;&nbsp;user_id&nbsp; &nbsp; &nbsp; UInt64,
&nbsp;&nbsp;&nbsp;&nbsp;page_path&nbsp; &nbsp; String,
&nbsp;&nbsp;&nbsp;&nbsp;referrer &nbsp; &nbsp; String,
&nbsp;&nbsp;&nbsp;&nbsp;device_type&nbsp; LowCardinality(String)
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_time)
ORDER BY (user_id, event_time);</code></pre>
<p><strong>2. Create the Kafka engine table (consumer interface, no data stored here)</strong></p>
<pre class="wp-block-code"><code>CREATE TABLE events.pageviews_kafka
(
&nbsp;&nbsp;&nbsp;&nbsp;event_time &nbsp; DateTime,
&nbsp;&nbsp;&nbsp;&nbsp;session_id &nbsp; UUID,
&nbsp;&nbsp;&nbsp;&nbsp;user_id&nbsp; &nbsp; &nbsp; UInt64,
&nbsp;&nbsp;&nbsp;&nbsp;page_path&nbsp; &nbsp; String,
&nbsp;&nbsp;&nbsp;&nbsp;referrer &nbsp; &nbsp; String,
&nbsp;&nbsp;&nbsp;&nbsp;device_type&nbsp; String
)
ENGINE = Kafka
SETTINGS
&nbsp;&nbsp;&nbsp;&nbsp;kafka_broker_list &nbsp; &nbsp; = 'kafka-broker1:9092,kafka-broker2:9092',
&nbsp;&nbsp;&nbsp;&nbsp;kafka_topic_list&nbsp; &nbsp; &nbsp; = 'analytics.pageviews',
&nbsp;&nbsp;&nbsp;&nbsp;kafka_group_name&nbsp; &nbsp; &nbsp; = 'clickhouse-analytics-consumer',
&nbsp;&nbsp;&nbsp;&nbsp;kafka_format&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = 'JSONEachRow',
&nbsp;&nbsp;&nbsp;&nbsp;kafka_num_consumers &nbsp; = 4,
&nbsp;&nbsp;&nbsp;&nbsp;kafka_skip_broken_messages = 5;</code></pre>
<p><strong>3. Materialized view: pipes rows from Kafka engine into MergeTree</strong></p>
<pre class="wp-block-code"><code>CREATE MATERIALIZED VIEW events.pageviews_mv
TO events.pageviews
AS
SELECT
&nbsp;&nbsp;&nbsp;&nbsp;event_time,
&nbsp;&nbsp;&nbsp;&nbsp;session_id,
&nbsp;&nbsp;&nbsp;&nbsp;user_id,
&nbsp;&nbsp;&nbsp;&nbsp;page_path,
&nbsp;&nbsp;&nbsp;&nbsp;referrer,
&nbsp;&nbsp;&nbsp;&nbsp;device_type
FROM events.pageviews_kafka;</code></pre>
<p>Once the materialized view is created, ClickHouse begins polling the Kafka topic automatically. Consumed messages are inserted into events.pageviews as MergeTree parts. Consumer offset tracking is handled by the Kafka consumer group; restart tolerance and at-least-once delivery are built in. Set kafka_skip_broken_messages to a non-zero value in production to prevent a malformed message from stalling the consumer.</p>
<h3 class="wp-block-heading">Pattern 2: Bulk load from CSV<a class="anchor-link" id="pattern-2-bulk-load-from-csv"></a></h3>
<p>For historical data migrations or batch loads, ClickHouse accepts CSV input directly from the command line or via its HTTP interface.</p>
<h4 class="wp-block-heading">Shell &ndash; bulk insert from CSV via clickhouse-client</h4>
<p><strong>1. Insert a CSV file with a header row into an existing table</strong></p>
<pre class="wp-block-code"><code>clickhouse-client 
&nbsp;&nbsp;&nbsp;&nbsp;--host ch-server.internal 
&nbsp;&nbsp;&nbsp;&nbsp;--port 9000 
&nbsp;&nbsp;&nbsp;&nbsp;--user analytics_writer 
&nbsp;&nbsp;&nbsp;&nbsp;--password &rdquo;${CH_PASSWORD}&rdquo; 
&nbsp;&nbsp;&nbsp;&nbsp;--query &rdquo;INSERT INTO events.pageviews FORMAT CSVWithNames&rdquo; 
&nbsp;&nbsp;&nbsp;&nbsp;&lt; /data/exports/pageviews_2024.csv</code></pre>
<p><strong>2. Alternatively, using the HTTP interface (suitable for remote or scripted loads)</strong></p>
<pre class="wp-block-code"><code>curl -X POST 
&rdquo;http://ch-server.internal:8123/?query=INSERT+INTO+events.pageviews+FORMAT+CSVWithNames&amp;user=analytics_writer&amp;password=${CH_PASSWORD}&rdquo; 
--data-binary @/data/exports/pageviews_2024.csv</code></pre>
<p>For large CSV loads, prefer splitting the file into chunks of 1&ndash;10 million rows and inserting each chunk as a separate INSERT batch. ClickHouse performs optimal part creation at insert batch sizes in this range. Very large single inserts, i.e. hundreds of millions of rows can produce oversized initial parts that take a long time to merge, potentially impacting query performance during the load.</p>
<h2 class="wp-block-heading">Case Study: E-Commerce Analytics Stack<a class="anchor-link" id="case-study-e-commerce-analytics-stack"></a></h2>
<p>An e-commerce example of integrating ClickHouse with MySQL and Kafka. A mid-sized platform uses this production stack:</p>
<ul class="wp-block-list">
<li>MySQL 8.0 (via ClusterControl) for transactional core data</li>
<li>Redis for session and cart state</li>
<li>Kafka for microservices event routing</li>
</ul>
<p>The challenge: analytical queries cause production latency in MySQL. The solution adds ClickHouse as a dedicated tier for real-time dashboards without altering existing deployments.</p>
<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="945" height="1024" src="https://severalnines.com/wp-content/uploads/2026/06/clickhouse-cdc-kafka-realtime-analytics-pipeline-945x1024.png" alt="Architecture diagram of a real-time data streaming pipeline featuring MySQL, Debezium CDC, Apache Kafka, ClickHouse, and Grafana, monitored by ClusterControl." class="wp-image-44181"></figure>
<h3 class="wp-block-heading">What changes for the operations team<a class="anchor-link" id="what-changes-for-the-operations-team"></a></h3>
<p>Adding ClickHouse introduces a new cluster to the managed estate. Since ClusterControl already handles the MySQL cluster, bringing ClickHouse under the same management plane ensures:</p>
<ul class="wp-block-list">
<li>ClickHouse and MySQL backups are configured and verified through a single interface.</li>
<li>Performance metrics for both ClickHouse and MySQL share the same Grafana dashboards.</li>
<li>Alerting for disk usage, replication lag, and Kafka consumer lag is centrally managed.</li>
</ul>
<p>While the platform engineering team oversees the Debezium connector and Kafka cluster, their performance remains critical for ClickHouse data freshness.</p>
<h2 class="wp-block-heading">Conclusion<a class="anchor-link" id="conclusion"></a></h2>
<p>ClickHouse fills a real gap: fast, scalable analytics over high-volume event data, at query latencies no row-oriented database can reach at scale. It doesn&rsquo;t replace MySQL or PostgreSQL; it sits beside them, consuming their change streams and your application events, and takes the analytical load off your transactional tier. For ops teams, four principles carry most of the weight:</p>
<ul class="wp-block-list">
<li>Treat pipeline health as a first-class concern. Your query results are only as fresh as the pipeline feeding them. Watch Kafka lag and CDC health with the same rigor you give replication lag.</li>
<li>Model for analytics, not normalization. Wide, denormalized tables are how ClickHouse wants to work. If denormalizing feels wrong to your relational instincts, that discomfort is usually the sign you&rsquo;re doing it right.</li>
<li>Plan for schema evolution early. Adding and dropping columns is cheap; changing column types is not. Have the procedure written before you need it.</li>
<li>Centralize management. Fragmented tooling fragments visibility. One plane for backups, alerts, and monitoring across the whole fleet pays for its setup cost quickly.</li>
</ul>
<p>ClickHouse isn&rsquo;t the answer to every workload. But if your analytical queries have outgrown your OLTP tier, it&rsquo;s one of the most direct and operationally manageable ways out. Start with a single node, replicate one or two high-value streams from Kafka, and measure. The added complexity is modest; the capability gained is not.</p>
<p>The post <a href="https://severalnines.com/blog/building-a-modern-analytics-stack-around-clickhouse/">Building a Modern Analytics Stack Around ClickHouse</a> appeared first on <a href="https://severalnines.com">Severalnines</a>.</p>

<p>The post <a rel="nofollow" href="https://severalnines.com/blog/building-a-modern-analytics-stack-around-clickhouse/">Building a Modern Analytics Stack Around ClickHouse</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Community Docker Images: keeping the operator open without a vendor registry lock-in</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/postgresql-community-images-operator/" />
      <id>https://www.percona.com/blog/postgresql-community-images-operator/</id>
      <updated>2026-06-30T14:09:35+00:00</updated>
      <author><name>Slava Sarzhan</name></author>
      <summary type="html"><![CDATA[<p>PostgreSQL community images address a real gap in how a Kubernetes database operator earns your trust. Running a database operator on Kubernetes means trusting two things: the code, and the container images the operator pulls. The code is on GitHub, easy to inspect, easy to fork. The container images, the registry that hosts them, and the … Continued<br />
The post Community Docker Images: keeping the operator open without a vendor registry lock-in appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/postgresql-community-images-operator/">Community Docker Images: keeping the operator open without a vendor registry lock-in</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><img decoding="async" loading="lazy" class="aligncenter wp-image-50112 size-large" src="https://www.percona.com/wp-content/uploads/2026/06/MDES-1088-Blog-image-Percona-Operator-for-PostgreSQL-V3.0.0-hero-1-1024x375.jpg" alt="" width="1024" height="375"></p>
<p><strong>PostgreSQL community images</strong>&nbsp;address a real gap in how a Kubernetes database operator earns your trust. Running a database operator on Kubernetes means trusting two things: the code, and the container images the operator pulls. The code is on GitHub, easy to inspect, easy to fork. The container images, the registry that hosts them, and the license that governs them all sit with the vendor, and any of those three can change without the source repository changing at all. Starting with Percona Operator for PostgreSQL 3.0.0, you can run the operator against community images you build yourself from the official PostgreSQL packages on download.postgresql.org, in a registry you control.</p>
<p>&nbsp;</p>
<div class="markdown-heading">
<h2 class="heading-element">TL;DR<a class="anchor-link" id="tldr"></a></h2>
</div>
<ul>
<li><strong>Community Docker Images: tech preview in PGO 3.0.0, official in 3.1.0.</strong>&nbsp;Point the operator at upstream-built PostgreSQL images instead of the Percona Distribution images.</li>
<li><strong>Build them yourself from the official PostgreSQL source.</strong>&nbsp;The Dockerfiles pull packages from download.postgresql.org (the PGDG repositories), so the trust chain runs from PGDG to your registry with no vendor in the middle.</li>
<li><strong>There are limits.</strong>&nbsp;Anything Percona-specific (TDE in our distribution build, for example) does not exist in an upstream-built image. That trade is intentional.</li>
</ul>
<p>In this post:</p>
<ul>
<li>How open source gets diluted in practice</li>
<li>Why distributions exist anyway, honestly</li>
<li>How Community Docker Images work</li>
<li>Limits of the upstream path</li>
<li>What to try, what to tell us</li>
</ul>
<p>&nbsp;</p>
<div class="markdown-heading">
&nbsp;
<h2 class="heading-element">How open source gets diluted<a class="anchor-link" id="how-open-source-gets-diluted"></a></h2>
</div>
<p>Open source has changed in the last few years, and not always for the better. Companies have learned that you can keep a project&rsquo;s source code fully open and still capture most of the lock-in by quietly closing the parts that matter in production: the release artifacts, the container images, the supported OS list, the certified Kubernetes distributions, the marketplace listings.</p>
<div class="markdown-heading">
&nbsp;
<h3><a class="anchor-link" id=""></a></h3>
<h3 class="heading-element">Same project, closed artifacts<a class="anchor-link" id="same-project-closed-artifacts"></a></h3>
</div>
<p>You can have a fully community CNCF project that does not appear on the Red Hat Marketplace except as a paid Enterprise edition. Similarly, you can have a vendor that ships one packaging in the community and a richer one in Enterprise with the features you actually need in production. The license still says &ldquo;open source.&rdquo; The practical experience says &ldquo;you depend on us.&rdquo; And the source repository&rsquo;s license is not the only license that matters here: a vendor can change the license, the trademark policy, or the distribution terms on the container images alone, while leaving the source repository untouched. That has happened in the PostgreSQL operator space recently, and the community noticed.</p>
<div class="markdown-heading">
&nbsp;
<h3><a class="anchor-link" id=""></a></h3>
<h3 class="heading-element">Why the community is right to be wary<a class="anchor-link" id="why-the-community-is-right-to-be-wary"></a></h3>
</div>
<p>Nobody outside the vendor can predict when a license will change, when a feature will move behind a paywall, or when an external contribution will get rejected because it competes with an Enterprise feature. Recent history has plenty of examples and the PostgreSQL community has been paying attention. When this community resists vendor-controlled distributions, it is not nostalgia. It is a rational read of where things have gone before.</p>
<p>I work on Percona&rsquo;s PostgreSQL operator, so I see this conversation from the vendor side. The skepticism is fair. The honest question for us is what to do about it.<br>
&nbsp;</p>
<h2><a class="anchor-link" id=""></a></h2>
<h2><strong>Why distributions exist anyway</strong><a class="anchor-link" id="why-distributions-exist-anyway"></a></h2>
<p><span style="font-weight: 400">Acknowledging the community&rsquo;s concerns does not mean distributions are pointless. There are real reasons to ship one, and pretending otherwise makes for bad blog posts.</span><br>
&nbsp;</p>
<h3><strong>What a distribution buys you</strong><a class="anchor-link" id="what-a-distribution-buys-you"></a></h3>
<p><span style="font-weight: 400">A vendor-built distribution lets the vendor:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Control the build process, dependencies, and defaults so they fit a specific user shape.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Ship hotfixes faster, because the whole release path sits in one place.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Fork PostgreSQL itself when something the upstream community will not accept, or can take years to accept, matters to customers, such as Transparent Data Encryption.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">For a Kubernetes operator, ship images with exactly the tools and extensions the operator supports, and skip everything else. The CVE surface stays smaller.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Give QA and Service teams a predictable environment. &ldquo;We support extensions A, B, C and not D, X, Z&rdquo; is only honest if QA actually exercises A, B, C and the Service team can work with them in the production environment.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Give customers one accountable party for the full release cycle, from hotfix through package availability. Some teams explicitly need that contract for compliance and audit reasons.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">And yes, less positive reasons that we covered above also apply, which is exactly the part the community keeps pointing at.</span></li>
</ol>
<p>&nbsp;</p>
<h3><strong>The trade-off you accept</strong><a class="anchor-link" id="the-trade-off-you-accept"></a></h3>
<p><span style="font-weight: 400">If you run the vendor distribution, you accept that the vendor&rsquo;s registry, image policy, and supported-extension matrix become part of your stack. If the vendor changes any of that, your operator deployment changes with it. That is not hypothetical for users who have lived through it on other products.</span></p>
<p><span style="font-weight: 400">So the real question is whether you can keep the benefits a distribution provides for the users who want them, while leaving an honest, supported door open for users who do not. That is the door PGO 3.0.0 opens.</span></p>
<p>&nbsp;</p>
<h2>Community PostgreSQL Images in PGO 3.0.0<a class="anchor-link" id="community-postgresql-images-in-pgo-3-0-0"></a></h2>
<p><span style="font-weight: 400">Starting with Percona Operator for PostgreSQL 3.0.0, the operator can run against images built from upstream PostgreSQL packages, not just the Percona Distribution images. This is what we are calling </span><b>Community PostgreSQL Images</b><span style="font-weight: 400">. In 3.0.0, the feature ships as a tech preview. In 3.1.0, these images become part of our official release cycle and are fully documented.</span></p>
<p><span style="font-weight: 400">One of the main advantages of Community Docker Images is that the community can request or contribute any extension that does not exist in the official Percona PostgreSQL distribution. TimescaleDB and Citus are the first examples: the community asked for them, and we shipped both in the Community Images set from day one.</span></p>
<p>&nbsp;</p>
<h2>How to use &ldquo;Community PostgreSQL images&rdquo;<a class="anchor-link" id="how-to-use-community-postgresql-images"></a></h2>
<p><span style="font-weight: 400">The operator does not care where the image came from, as long as the image meets the operator&rsquo;s runtime expectations&nbsp;</span></p>
<p><span style="font-weight: 400">A typical CR using a community image looks like this:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: pgv2.percona.com/v2
kind: PerconaPGCluster
metadata:
  name: cluster1
spec:
  image: registry.example.com/postgresql-community:18
  postgresVersion: 18
  proxy:
    pgBouncer:
      image: registry.example.com/pgbouncer-community:1.23
  backups:
    pgbackrest:
      image: registry.example.com/pgbackrest-community:2.51
  # other spec fields unchanged from a normal CR</pre>
<p><span style="font-weight: 400">The fields that change are </span><span style="font-weight: 400">spec.image</span><span style="font-weight: 400">, </span><span style="font-weight: 400">spec.proxy.pgBouncer.image</span><span style="font-weight: 400">, and </span><span style="font-weight: 400">spec.backups.pgbackrest.image</span><span style="font-weight: 400">. You can build and publish all three images under your own registry, with your own tags if that helps you track versions. The operator drives the rest of the deployment the same way it always has: instances, backups, replication, monitoring, all of it.</span></p>
<p>&nbsp;</p>
<h3>What ships are in each image<a class="anchor-link" id="what-ships-are-in-each-image"></a></h3>
<p><span style="font-weight: 400">Each Community Docker Image is a thin layer over the chosen base (UBI9 or UBI8) plus the packages the operator needs for that role. Where you see </span><span style="font-weight: 400">{N}</span><span style="font-weight: 400">, substitute the PostgreSQL major you build for (17, 18, and so on).<br>
</span></p>
<p><strong><code>postgres</code>&nbsp;image</strong>&nbsp;(e.g.&nbsp;<code>postgres17</code>):</p>
<table>
<thead>
<tr>
<th>Package</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>postgresql{N}-server</code></td>
<td>PostgreSQL server</td>
</tr>
<tr>
<td><code>postgresql{N}-contrib</code></td>
<td>contrib modules</td>
</tr>
<tr>
<td><code>pg_repack_{N}</code></td>
<td>online table/index reorganization</td>
</tr>
<tr>
<td><code>pgaudit_{N}</code></td>
<td>audit logging</td>
</tr>
<tr>
<td><code>set_user_{N}</code></td>
<td>privilege escalation control</td>
</tr>
<tr>
<td><code>pgvector_{N}</code></td>
<td>vector similarity search</td>
</tr>
<tr>
<td><code>wal2json_{N}</code></td>
<td>WAL to JSON logical decoding</td>
</tr>
<tr>
<td><code>pg_cron_{N}</code></td>
<td>in-database cron scheduler</td>
</tr>
<tr>
<td><code>pgbackrest&amp;lt;/code&gt;</code></td>
<td>backup/restore tool</td>
</tr>
<tr>
<td><code>patroni</code></td>
<td>HA cluster manager</td>
</tr>
<tr>
<td><code>timescaledb-2-postgresql-{N}</code></td>
<td>time-series extension (x86_64 only; EL9 only for PG18)</td>
</tr>
<tr>
<td><code>citus_{N}</code></td>
<td>distributed PostgreSQL (PG16+ only)</td>
</tr>
</tbody>
</table>
<p><strong><code>pgbackrest</code>&nbsp;image</strong>:</p>
<table>
<thead>
<tr>
<th>Package</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>pgbackrest</code></td>
<td>backup/restore tool only</td>
</tr>
</tbody>
</table>
<p><strong><code>pgbouncer</code>&nbsp;image</strong>:</p>
<table>
<thead>
<tr>
<th>Package</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>pgbouncer</code></td>
<td>connection pooler only</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p><span style="font-weight: 400">The split is intentional. The postgres image ships the full operator-aware runtime. The backup and proxy images stay minimal. As a result, the operator&rsquo;s components are in separate failure domains and shrink the attack surface of each container.</span></p>
<p>&nbsp;</p>
<h3>Limits worth being honest about<a class="anchor-link" id="limits-worth-being-honest-about"></a></h3>
<p><span style="font-weight: 400">A community image is not a Percona Distribution image. Two practical consequences:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Distribution-only features will not work. Transparent Data Encryption, for example, lives in the Percona Distribution build. A community image built from upstream PostgreSQL does not include it. If you depend on TDE, run the distribution image.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Support boundaries are different. Percona Support is responsible for the Percona Distribution images and the operator code. A community image you built yourself </span></li>
</ul>
<p><span style="font-weight: 400">Ultimately, these are the right trade-offs. The point of community images is to give you transparency and control. Taking care of your own image is part of that deal. At the same time, we publish all three images under </span><span style="font-weight: 400">perconalab/percona-postgresql-operator</span><span style="font-weight: 400"> on Docker Hub so you can evaluate the tech preview without standing up your own build pipeline first. </span><span style="font-weight: 400">perconalab</span><span style="font-weight: 400"> is Percona&rsquo;s non-production namespace, so use those images for testing. For production, build and sign your own.</span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400">UBI9 (EL9):</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">docker.io/perconalab/percona-postgresql-operator:main-postgres14-community
docker.io/perconalab/percona-postgresql-operator:main-postgres15-community
docker.io/perconalab/percona-postgresql-operator:main-postgres16-community
docker.io/perconalab/percona-postgresql-operator:main-postgres17-community
docker.io/perconalab/percona-postgresql-operator:main-postgres18-community
docker.io/perconalab/percona-postgresql-operator:main-pgbackrest-community
docker.io/perconalab/percona-postgresql-operator:main-pgbouncer-community
docker.io/perconalab/percona-postgresql-operator:main-upgrade-community</pre>
<p><span style="font-weight: 400">UBI8 (EL8):</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">docker.io/perconalab/percona-postgresql-operator:main-ubi8-postgres14-community
docker.io/perconalab/percona-postgresql-operator:main-ubi8-postgres15-community
docker.io/perconalab/percona-postgresql-operator:main-ubi8-postgres16-community
docker.io/perconalab/percona-postgresql-operator:main-ubi8-postgres17-community
docker.io/perconalab/percona-postgresql-operator:main-ubi8-postgres18-community
docker.io/perconalab/percona-postgresql-operator:main-ubi8-upgrade-community</pre>
<p>&nbsp;</p>
<h3>How to build the images<a class="anchor-link" id="how-to-build-the-images"></a></h3>
<p><span style="font-weight: 400">The Dockerfile, the package list, and a sample CI job ship in </span><a href="https://github.com/percona/percona-docker/tree/main/postgresql-containers/community"><span style="font-weight: 400">percona-docker/postgresql-containers/community.</span></a><span style="font-weight: 400"> The build is a regular </span><span style="font-weight: 400">make</span><span style="font-weight: 400"> target on top of </span><span style="font-weight: 400">docker buildx</span><span style="font-weight: 400">, so you can run it on any multi-platform builder.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag"># Prerequisites: docker buildx with a multi-platform builder
docker buildx create --use --name multiarch

# Build and push all PostgreSQL community images (UBI9 / EL9)
git clone https://github.com/percona/percona-docker
cd percona-docker/postgresql-containers/community
make all TAG=1.0.0 REGISTRY=myrepo/percona-postgresql-operator

# Or a single image
make postgres17 TAG=1.0.0 REGISTRY=myrepo/percona-postgresql-operator

# UBI8 / EL8 variants
make all-ubi8 TAG=1.0.0-ubi8 REGISTRY=myrepo/percona-postgresql-operator</pre>
<p><em><span style="font-weight: 400">make all</span></em><span style="font-weight: 400"> builds all three images (postgres, pgBouncer, pgBackRest) so they stay version-aligned. Override </span><em><span style="font-weight: 400">REGISTRY</span></em><span style="font-weight: 400"> and </span><em><span style="font-weight: 400">TAG</span></em><span style="font-weight: 400"> to point at your own namespace and tagging scheme. Once the images are in your registry, plug them into the CR fields shown earlier, and the operator picks them up.</span></p>
<p><span style="font-weight: 400">Full build documentation: </span><a href="https://github.com/percona/percona-docker/blob/main/postgresql-containers/community/README.md"><span style="font-weight: 400">percona-docker/postgresql-containers/community/README.md.</span></a></p>
<p>&nbsp;</p>
<h3>How to contribute<a class="anchor-link" id="how-to-contribute"></a></h3>
<p><span style="font-weight: 400">Community images live in </span><a href="https://github.com/percona/percona-docker"><span style="font-weight: 400">percona/percona-docker</span></a><span style="font-weight: 400">, and the build is driven by a </span><span style="font-weight: 400">transform.py</span><span style="font-weight: 400"> generator that produces the Dockerfiles under </span><span style="font-weight: 400">build/</span><span style="font-weight: 400">. The files under </span><span style="font-weight: 400">build/</span><span style="font-weight: 400"> are regenerated on every sync, so contributions go through the generator, never through the generated files.</span></p>
<p><span style="font-weight: 400">Full contribution guide: </span><a href="https://github.com/percona/percona-docker/blob/main/postgresql-containers/community/CONTRIBUTING.md"><span style="font-weight: 400">community/CONTRIBUTING.md</span></a><span style="font-weight: 400">.</span></p>
<p>&nbsp;</p>
<h3>How to provide feedback<a class="anchor-link" id="how-to-provide-feedback"></a></h3>
<p><span style="font-weight: 400">Two channels, depending on the shape of the feedback:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">GitHub issue on </span><a href="https://github.com/percona/percona-postgresql-operator"><span style="font-weight: 400">percona/percona-postgresql-operator</span></a><span style="font-weight: 400"> with the </span><span style="font-weight: 400">community-images</span><span style="font-weight: 400"> label. Use this for bug reports, missing extensions, build problems, and concrete requests. The label keeps all <em>community-image</em> reports in one filter the team watches.</span></li>
</ul>
<p>&nbsp;</p>
<h2>What&rsquo;s next<a class="anchor-link" id="whats-next"></a></h2>
<p><span style="font-weight: 400">The first step was taking full engineering ownership of Percona Operator for PostgreSQL as an independent project, so the roadmap, the release cadence, and the governance live with one team that the community can talk to directly. Community </span><b>PostgreSQL</b><span style="font-weight: 400"> Images are the next step in that same commitment. If the community adopts this path, we have ideas for what to invest in next.</span></p>
<p><span style="font-weight: 400">We will let the community tell us. If this is useful, we keep investing here. We are ready to add more features to the operator around </span><b>Community Images</b><span style="font-weight: 400">. Conversely, if nobody adopts it, that is also a signal, and an honest one.</span></p>
<p><span style="font-weight: 400">Try the tech preview in 3.0.0. Open an issue if the build flow is rougher than it should be. Tell us what you want next on the forum or directly on GitHub.</span></p>
<p>&nbsp;</p>
<h2>Try It Out<a class="anchor-link" id="try-it-out"></a></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Percona Operator for PostgreSQL docs: </span><a href="https://docs.percona.com/percona-operator-for-postgresql/"><span style="font-weight: 400">https://docs.percona.com/percona-operator-for-postgresql/</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">GitHub: </span><a href="https://github.com/percona/percona-postgresql-operator"><span style="font-weight: 400">https://github.com/percona/percona-postgresql-operator</span></a></li>
<li><span style="font-weight: 400">Community Forum: </span><a href="https://forums.percona.com/"><span style="font-weight: 400">https://forums.percona.com</span></a><span style="font-weight: 400">, share your feedback, ask questions, or report issues</span></li>
</ul>
<p>The post <a href="https://www.percona.com/blog/postgresql-community-images-operator/">Community Docker Images: keeping the operator open without a vendor registry lock-in</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/postgresql-community-images-operator/">Community Docker Images: keeping the operator open without a vendor registry lock-in</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Debugging with Ephemeral Containers</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/debugging-with-ephemeral-containers/" />
      <id>https://www.percona.com/blog/debugging-with-ephemeral-containers/</id>
      <updated>2026-06-30T13:52:25+00:00</updated>
      <author><name>Chetan Shivashankar</name></author>
      <summary type="html"><![CDATA[<p>Debugging applications in Kubernetes can be tricky. Containers are designed to be small, immutable, and purpose-built. That is great for production, but not always ideal when something breaks. Many production images are minimal or distroless. They may not include tools that are useful for troubleshooting. In some cases, the application container may already be crashing, … Continued<br />
The post Debugging with Ephemeral Containers appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/debugging-with-ephemeral-containers/">Debugging with Ephemeral Containers</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">Debugging applications in Kubernetes can be tricky. Containers are designed to be small, immutable, and purpose-built. That is great for production, but not always ideal when something breaks. Many production images are minimal or distroless. They may not include tools that are useful for troubleshooting.</span></p>
<p><span style="font-weight: 400">In some cases, the application container may already be crashing, which means </span><span style="font-weight: 400">kubectl exec</span><span style="font-weight: 400"> is not useful. In other cases, accessing the nodes may not be possible. Since Pods are immutable, it is impossible to add another container for troubleshooting.</span></p>
<p><span style="font-weight: 400">This is where ephemeral containers help.</span></p>
<h2><span style="font-weight: 400">What Are Ephemeral Containers?</span><a class="anchor-link" id="what-are-ephemeral-containers"></a></h2>
<p><span style="font-weight: 400">In Kubernetes, </span><b>ephemeral containers</b><span style="font-weight: 400"> are a special type of container designed to run temporarily inside an existing Pod. Their primary purpose is to help administrators and developers troubleshoot, inspect, and debug live applications without disrupting the running service. They are </span><b>not</b><span style="font-weight: 400"> meant to run application workloads. Instead, they are designed for operational debugging.</span></p>
<p><span style="font-weight: 400">An ephemeral container is not added to a Pod by editing <span style="color: #0000ff"><code></code></span></span><span style="font-weight: 400;color: #0000ff">spec.containers</span><span style="font-weight: 400"><span style="color: #0000ff">.</span> Kubernetes treats it differently from regular containers. When an ephemeral container is created, a request is sent to the Kubernetes API server using the Pod&rsquo;s <code></code></span><span style="font-weight: 400"><span style="color: #0000ff">ephemeralcontainers</span></span><span style="font-weight: 400"> subresource. This distinction is important because most of a Pod&rsquo;s spec is immutable after creation; Kubernetes does not allow you to simply edit a running Pod and append a normal container to <span style="color: #0000ff"><code></code></span></span><span style="font-weight: 400;color: #0000ff">spec.containers</span><span style="font-weight: 400">.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">spec:
  ephemeralContainers:
  - name: &lt;debug container-name&gt;
    image: &lt;image&gt;
    targetContainerName: &lt;&gt; # (Optional)If ephemeral container needs to have same pid namespace of a running container.</pre>

<h2><span style="font-weight: 400">Key Characteristics of Ephemeral Containers</span><a class="anchor-link" id="key-characteristics-of-ephemeral-containers"></a></h2>
<p><span style="font-weight: 400">Unlike standard containers, ephemeral containers have the following characteristics:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">They do not have guaranteed CPU or memory resources (</span><span style="font-weight: 400">limits</span><span style="font-weight: 400"> or </span><span style="font-weight: 400">requests</span><span style="font-weight: 400">).</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">If an ephemeral container crashes or completes its task, it will never restart.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">They do not include fields such as ports, </span><span style="font-weight: 400">livenessProbe</span><span style="font-weight: 400">, or </span><span style="font-weight: 400">readinessProbe</span><span style="font-weight: 400">.</span></li>
</ol>
<h2><span style="font-weight: 400">Examples</span><a class="anchor-link" id="examples"></a></h2>
<p><span style="font-weight: 400">For testing, the Percona Operator for MySQL based on XtraDB Cluster is installed. The installation steps can be found </span><a href="https://docs.percona.com/percona-operator-for-mysql/pxc/kubectl.html"><span style="font-weight: 400">here</span></a><span style="font-weight: 400">. Once the installation is complete, the running pods should look similar to the following:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag"># kubectl get po
NAME                                              READY   STATUS      RESTARTS   AGE
cluster1-haproxy-0                                2/2     Running     0          17h
cluster1-haproxy-1                                2/2     Running     0          17h
cluster1-haproxy-2                                2/2     Running     0          16h
cluster1-pxc-0                                    3/3     Running     0          17h
cluster1-pxc-1                                    3/3     Running     0          17h
cluster1-pxc-2                                    3/3     Running     0          16h
percona-xtradb-cluster-operator-6b5f75f65-fpjxr   1/1     Running     0          17h
xb-cron-cluster1-fs-pvc-20266250025-372f8-hmrmh   0/1     Completed   0          7h9m</pre>
<p><b>NOTE: It is important to note that this behavior depends on your environment, specifically whether you have the required privileges or Security Context Constraints (SCC) in place. The commands below are for demonstration purposes and do not necessarily follow all best practices, such as avoiding generic <code>ubuntu</code> images, long-running shells like <code>bash</code>, or running containers as root.</b><b>Always run ephemeral containers with a strong focus on security, especially in production systems.</b></p>
<p><span style="font-weight: 400">Let&rsquo;s look at some examples of how ephemeral containers can be useful.</span></p>
<h2><span style="font-weight: 400">1. Ephemeral Container with shared network namespace</span><a class="anchor-link" id="1-ephemeral-container-with-shared-network-namespace"></a></h2>
<p><span style="font-weight: 400">When an ephemeral container is created in a Pod, it shares the network namespace of all other containers in that Pod. This is particularly useful for troubleshooting network-related issues.</span></p>
<p><span style="font-weight: 400">Let&rsquo;s create an ephemeral container named <code></code></span><span style="font-weight: 400"><span style="color: #0000ff">debug-1</span></span><span style="font-weight: 400"> in the MySQL pod <code></code></span><span style="font-weight: 400"><span style="color: #0000ff">cluster1-pxc-0</span></span><span style="font-weight: 400">:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">% kubectl debug pod/cluster1-pxc-0 --image=ubuntu --container=debug-1 -ti -- bash
All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.
If you don't see a command prompt, try pressing enter.</pre>
<p><span style="font-weight: 400">When we check the processes in the container, only the bash process that was started when the container was created is running.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@cluster1-pxc-0:/# ps -elf
F S UID          PID    PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
4 S root           1       0  0  80   0 -  1192 do_wai 07:16 pts/0    00:00:00 bash
4 R root           9       1  0  80   0 -  1701 -      07:16 pts/0    00:00:00 ps -elf</pre>
<p><span style="font-weight: 400">However, port 3306 is open because the MySQL process in the primary container shares the same network namespace as our debug container.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@cluster1-pxc-0:/# netstat -tlnp | grep :3306
tcp        0      0 10.42.0.18:33062        0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:3306            0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::33060                :::*                    LISTEN      -</pre>
<p><span style="font-weight: 400">This capability is highly effective for analyzing network traffic, egress connectivity, and local service availability.</span><span style="font-weight: 400">Let&rsquo;s examine the Pod spec and status to see how ephemeral containers are represented.</span></p>
<p><span style="font-weight: 400">The following is the spec of the ephemeral container. Note the <code><span style="color: #0000ff">securityContext</span></code></span>, which we will discuss later in this post:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">% kubectl get po cluster1-pxc-0 -oyaml | yq .spec.ephemeralContainers    
- command:
    - bash
  image: ubuntu
  imagePullPolicy: Always
  name: debug-1
  resources: {}
  securityContext:
    capabilities:
      add:
        - SYS_PTRACE
  stdin: true
  terminationMessagePath: /dev/termination-log
  terminationMessagePolicy: File
  tty: true</pre>
<p><span style="font-weight: 400">Status of ephemeral containers</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">% kubectl get po cluster1-pxc-0 -oyaml | yq .status.ephemeralContainerStatuses
- containerID: containerd://4f656ada1679595109a9ac4c5bae916dc35404d7a45818eacef83aabd6d8509b
  image: docker.io/library/ubuntu:latest
  imageID: docker.io/library/ubuntu@sha256:53958ec7b67c2c9355df922dd08dbf0360611f8c3cdb656875e81873db9ffdba
  lastState: {}
  name: debug-1
  ready: false
  resources: {}
  restartCount: 0
  state:
    running:
      startedAt: "2026-06-25T07:16:22Z"
  user:
    linux:
      gid: 0
      supplementalGroups:
        - 0
        - 1001
      uid: 0</pre>

<h2><span style="font-weight: 400">2. Ephemeral Container with shared network namespace, pid namespace</span><a class="anchor-link" id="2-ephemeral-container-with-shared-network-namespace-pid-namespace"></a></h2>
<p><span style="font-weight: 400">While sharing the network namespace is useful, sharing the PID namespace to inspect running processes can be beneficial in many debugging scenarios.</span></p>
<p><span style="font-weight: 400">Let&rsquo;s create an ephemeral container named <code></code></span><span style="font-weight: 400"><span style="color: #0000ff">debug-2</span></span><span style="font-weight: 400"> in the <code></code></span><span style="font-weight: 400"><span style="color: #0000ff">cluster1-pxc-0</span></span><span style="font-weight: 400"> Pod, specifically targeting the PID namespace of the <code></code></span><span style="font-weight: 400"><span style="color: #0000ff">pxc</span></span><span style="font-weight: 400"> container:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">% kubectl debug pod/cluster1-pxc-0 --image=ubuntu --container=debug-2 --target=pxc -ti -- bash
Targeting container "pxc". If you don't see processes from this container it may be because the container runtime doesn't support this feature.
All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.
If you don't see a command prompt, try pressing enter.</pre>
<p><span style="font-weight: 400">A key change from the previous command is the addition of the <code></code></span><span style="font-weight: 400"><span style="color: #0000ff">--target=pxc</span></span><span style="font-weight: 400"> flag. This creates an ephemeral container that shares the PID namespace of the </span><span style="font-weight: 400">pxc</span><span style="font-weight: 400"> container.</span></p>
<p><span style="font-weight: 400">When we check the processes in the container, we can see the MySQL processes.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@cluster1-pxc-0:/# ps -elf
F S UID          PID    PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
4 S 1001           1       0  4  80   0 - 832724 do_pol Jun24 ?       00:50:54 mysqld --wsrep_start_position=9d4e0c3e-6fd5-11f1-aab4-1a8a26ce607c:28
4 S 1001          90       1  0  80   0 - 306929 futex_ Jun24 ?       00:00:00 /var/lib/mysql/mysql-state-monitor
1 Z 1001        2999       1  0  80   0 -     0 -      Jun24 ?        00:00:00 [wsrep_sst_xtrab] &lt;defunct&gt;
4 S root      173053       0  0  80   0 -  1192 do_wai 08:01 pts/0    00:00:00 bash
4 R root      173081  173053  0  80   0 -  1701 -      08:01 pts/0    00:00:00 ps -elf</pre>
<p><span style="font-weight: 400">We can also verify this by network stats</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@cluster1-pxc-0:/# netstat -anlp | grep 3306
tcp        0      0 10.42.0.18:33062        0.0.0.0:*               LISTEN      1/mysqld            
tcp        0      0 0.0.0.0:3306            0.0.0.0:*               LISTEN      1/mysqld            
tcp        0      0 10.42.0.18:59330        10.42.0.18:33062        TIME_WAIT   -                   
tcp        0      0 10.42.0.18:33062        10.42.1.4:52464         TIME_WAIT   -                   
tcp        0      0 10.42.0.18:36008        10.42.0.18:33062        TIME_WAIT   -                   
tcp        0      0 10.42.0.18:33062        10.42.1.4:52448         TIME_WAIT   -                   
tcp        0      0 10.42.0.18:33756        10.42.0.18:33062        TIME_WAIT   -                   
tcp        0      0 10.42.0.18:43086        10.42.0.18:33062        TIME_WAIT   -                   
tcp        0      0 10.42.0.18:43206        10.42.0.18:33062        TIME_WAIT   -                   
tcp        0      0 10.42.0.18:44256        10.42.0.18:33062        TIME_WAIT   -                   
tcp        0      0 10.42.0.18:43220        10.42.0.18:33062        TIME_WAIT   -                   
tcp        0      0 10.42.0.18:44258        10.42.0.18:33062        TIME_WAIT   -                   
tcp6       0      0 :::33060                :::*                    LISTEN      1/mysqld</pre>

<h2>3. <span style="font-weight: 400">Ephemeral Container with shared network namespace, shared pid namespace, shared volume</span><a class="anchor-link" id="3-ephemeral-container-with-shared-network-namespace-shared-pid-namespace-shared-volume"></a></h2>
<p><span style="font-weight: 400">In some cases, you may need to collect dumps, check database files, or inspect logs, which requires access to the filesystem. However, each container has its own mount namespace, and mount namespaces cannot be shared directly.</span><span style="font-weight: 400">A volume mounted in a Pod, however, can be shared across containers, including ephemeral containers.</span></p>
<p><span style="font-weight: 400">Let&rsquo;s check the volumes present in the <code></code></span><span style="font-weight: 400;color: #0000ff">cluster1-pxc-0</span><span style="font-weight: 400"> pod and how they are mounted to the </span><span style="font-weight: 400">pxc</span><span style="font-weight: 400"> container.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">% kubectl get po cluster1-pxc-0 -o yaml | yq '.spec.volumes[] | select(.name == "datadir")'
name: datadir
persistentVolumeClaim:
  claimName: datadir-cluster1-pxc-0

% kubectl get po cluster1-pxc-0 -o yaml | yq '.spec.containers[] | select(.name == "pxc")| .volumeMounts[] | select(.name == "datadir")' 
mountPath: /var/lib/mysql
name: datadir</pre>
<p><span style="font-weight: 400">As seen above, the persistent volume holding the database files is mounted at </span><b>/var/lib/mysql</b><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">Let&rsquo;s create an ephemeral container named <code></code></span><span style="font-weight: 400"><span style="color: #0000ff">debug-3</span></span><span style="font-weight: 400"> in the <code></code></span><span style="font-weight: 400"><span style="color: #0000ff">cluster1-pxc-0</span></span><span style="font-weight: 400"> pod, sharing the PID namespace of the </span><span style="font-weight: 400">pxc</span><span style="font-weight: 400"> container and mounting the </span><span style="font-weight: 400">datadir</span><span style="font-weight: 400"> volume at <code></code></span><span style="font-weight: 400"><span style="color: #0000ff">/db-mount</span></span><span style="font-weight: 400">:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">% kubectl patch po cluster1-pxc-0 --subresource=ephemeralcontainers -p '
{
    "spec":
    {
        "ephemeralContainers":
        [
            {
                "name": "debug-3",
                "command": ["bash"],
                "image": "ubuntu",
                "targetContainerName": "pxc",
                "stdin": true,
                "tty": true,
                "volumeMounts": [{
                    "mountPath": "/db-mount",
                    "name": "datadir",
                    "readOnly": true
                }]
            }
        ]
    }
}'
pod/cluster1-pxc-0 patched</pre>
<p><span style="font-weight: 400">The command above creates the ephemeral container in the pod. To access it, we will attach to the running debug container:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">% kubectl attach -ti pod/cluster1-pxc-0 -c debug-3
All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.
If you don't see a command prompt, try pressing enter.
root@cluster1-pxc-0:/# cd /db-mount
root@cluster1-pxc-0:/db-mount# ls
'#ib_16384_0.dblwr'                 binlog.000001        gvwstate.dat                 mysql                     mysqlx.sock.lock       pxc-entrypoint.sh
'#ib_16384_1.dblwr'                 binlog.000002        ib_buffer_pool               mysql-state-monitor       notify.sock            readiness-check.sh
 '#innodb_redo'                     binlog.000003        ibdata1                      mysql-state-monitor.log   peer-list              sys
 '#innodb_temp'                     binlog.index         ibtmp1                       mysql.ibd                 performance_schema     undo_001
 audit_filter.20260624T140457.log   cluster1-pxc-0.pid   innobackup.backup.full.log   mysql.state               pmm-prerun.sh          undo_002
 audit_filter.log                   galera.cache         innobackup.backup.log        mysql_upgrade_history     private_key.pem        version_info
 auth_plugin                        get-pxc-state        liveness-check.sh            mysqld-error.log          public_key.pem         wsrep_cmd_notify_handler.sh
 auto.cnf                           grastate.dat         logrotate.status             mysqlx.sock               pxc-configure-pxc.sh</pre>
<p><span style="font-weight: 400">As demonstrated, the database files located at /var/lib/mysql are now accessible at /db-mount within the <code></code></span><span style="font-weight: 400"><span style="color: #0000ff">debug-3</span></span><span style="font-weight: 400"> container.</span></p>
<p><span style="font-weight: 400">Since the volume was mounted with the </span><span style="color: #0000ff"><b>&ldquo;readOnly&rdquo;: true</b></span><span style="font-weight: 400"> parameter, no writes can be performed.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@cluster1-pxc-0:/db-mount# touch test
touch: cannot touch 'test': Read-only file system</pre>
<p><span style="font-weight: 400">If you require write permissions, simply omit the </span><span style="color: #0000ff"><b>&ldquo;readOnly&rdquo;: true</b></span><span style="font-weight: 400"> flag.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">% kubectl patch po cluster1-pxc-0 --subresource=ephemeralcontainers -p '
{
    "spec":
    {
        "ephemeralContainers":
        [
            {
                "name": "debug-4",
                "command": ["bash"],
                "image": "ubuntu",
                "targetContainerName": "pxc",
                "stdin": true,
                "tty": true,
                "volumeMounts": [{
                    "mountPath": "/db-mount",
                    "name": "datadir"
                }]
            }
        ]
    }
}'
pod/cluster1-pxc-0 patched</pre>
<p><span style="font-weight: 400">Now, attach to the container and create a file in the volume mount:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">% kubectl attach -ti pod/cluster1-pxc-0 -c debug-4                  
All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.
If you don't see a command prompt, try pressing enter.
root@cluster1-pxc-0:/# touch /db-mount/test
root@cluster1-pxc-0:/# ls /db-mount/test
/db-mount/test</pre>

<h2><span style="font-weight: 400">4. Ephemeral Container on a Kubernetes node&rsquo;s namespace and filesystem</span><a class="anchor-link" id="4-ephemeral-container-on-a-kubernetes-nodes-namespace-and-filesystem"></a></h2>
<p><span style="font-weight: 400">If you need to examine system logs (such as kernel logs or dmesg) but do not have SSH access to the nodes, you can run an ephemeral container directly on the node&rsquo;s namespace and filesystem.</span></p>
<p><span style="font-weight: 400">Let&rsquo;s check the nodes of the Kubernetes cluster and run an ephemeral container directly on one:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">% kubectl get nodes
NAME                 STATUS   ROLES                AGE   VERSION
chetan-1-36-node-1   Ready    control-plane,etcd   6d    v1.36.1+k3s1
chetan-1-36-node-2   Ready    control-plane,etcd   6d    v1.36.1+k3s1
chetan-1-36-node-3   Ready    control-plane,etcd   6d    v1.36.1+k3s1</pre>
<p><span style="font-weight: 400">Run an ephemeral container:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">% kubectl debug node/chetan-1-36-node-1 --image=ubuntu --container=debug-5 -ti -- bash
Creating debugging pod node-debugger-chetan-1-36-node-1-hfgms with container debug-5 on node chetan-1-36-node-1.
All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.
If you don't see a command prompt, try pressing enter.
root@chetan-1-36-node-1:/#</pre>
<p><span style="font-weight: 400">Node&rsquo;s filesystem can be accessed at /host.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@chetan-1-36-node-1:/#  ls /host
bin                boot  etc   lib                lib64       media  opt   root  sbin                snap  swapfile  tmp  var
bin.usr-is-merged  dev   home  lib.usr-is-merged  lost+found  mnt    proc  run   sbin.usr-is-merged  srv   sys       usr
root@chetan-1-36-node-1:/#  ls /host/var/log/dmesg 
/host/var/log/dmesg</pre>
<p><span style="font-weight: 400">Behind the scenes, a Pod with the name </span><span style="font-weight: 400">node-debugger-&lt;node-name&gt;-&lt;hash&gt;</span><span style="font-weight: 400"> is created.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">% kubectl get po -l app.kubernetes.io/managed-by=kubectl-debug
NAME                                     READY   STATUS      RESTARTS   AGE
node-debugger-chetan-1-36-node-1-hfgms   0/1     Completed   0          33m</pre>
<p><span style="font-weight: 400">Let&rsquo;s check the spec of the debug pod</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">% kubectl get po -l app.kubernetes.io/managed-by=kubectl-debug
NAME                                     READY   STATUS      RESTARTS   AGE
node-debugger-chetan-1-36-node-1-hfgms   0/1     Completed   0          33m

Let&rsquo;s check the spec of the debug pod

% kubectl get po node-debugger-chetan-1-36-node-1-hfgms -oyaml | yq .spec
containers:
  - command:
      - bash
    image: ubuntu
    imagePullPolicy: Always
    name: debug-5
    resources: {}
    stdin: true
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    tty: true
    volumeMounts:
      - mountPath: /host
        name: host-root
      - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
        name: kube-api-access-4lj9h
        readOnly: true
dnsPolicy: ClusterFirst
enableServiceLinks: true
hostIPC: true
hostNetwork: true
hostPID: true
nodeName: chetan-1-36-node-1
preemptionPolicy: PreemptLowerPriority
priority: 0
restartPolicy: Never
schedulerName: default-scheduler
securityContext: {}
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 30
tolerations:
  - operator: Exists
volumes:
  - hostPath:
      path: /
      type: ""
    name: host-root
  - name: kube-api-access-4lj9h
    projected:
      defaultMode: 420
      sources:
        - serviceAccountToken:
            expirationSeconds: 3607
            path: token
        - configMap:
            items:
              - key: ca.crt
                path: ca.crt
            name: kube-root-ca.crt
        - downwardAPI:
            items:
              - fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.namespace
                path: namespace</pre>
<p><span style="font-weight: 400">Some key observations from the spec are the following:&nbsp;</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">hostIPC: true  -&gt; Share host&rsquo;s IPC namespace
hostNetwork: true  -&gt; Share Host Node Network
hostPID: true  -&gt; Share host&rsquo;s PID namespace

volumes:
  - hostPath:
      path: /      -&gt; Use Host&rsquo;s file system 
      type: ""
    name: host-root

    volumeMounts:
      - mountPath: /host
        name: host-root</pre>
<p><span style="font-weight: 400">The specifications above indicate excessive permissions for a regular application; consequently, these might be disabled by your system administrator via security policies.</span></p>
<h2><span style="font-weight: 400">Profile of an Ephemeral Container</span><a class="anchor-link" id="profile-of-an-ephemeral-container"></a></h2>
<p><span style="font-weight: 400">An interesting option for the </span><span style="font-weight: 400">kubectl debug</span><span style="font-weight: 400"> command is <code></code></span><span style="font-weight: 400"><span style="color: #0000ff">--profile</span></span><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">The official documentation provides the following:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">--profile string     Default: "general"
Options are "general", "baseline", "restricted", "netadmin" or "sysadmin". Defaults to "general"</pre>
<p><span style="font-weight: 400">These profiles define the </span><a href="https://man7.org/linux/man-pages/man7/capabilities.7.html"><span style="font-weight: 400">capabilities</span></a><span style="font-weight: 400"> associated with the </span><a href="https://kubernetes.io/docs/tasks/configure-pod-container/security-context/"><span style="font-weight: 400">SecurityContext</span></a><span style="font-weight: 400"> and determine how the host&rsquo;s filesystem and namespaces are accessed. The security context details for each profile are listed below; further details are available in the </span><a href="https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/debug/profiles.go"><span style="font-weight: 400">source code</span></a><span style="font-weight: 400">.</span></p>
<table dir="ltr" border="1" cellspacing="0" cellpadding="0" data-sheets-root="1" data-sheets-baot="1">
<colgroup>
<col width="157">
<col width="355">
<col width="291"></colgroup>
<tbody>
<tr>
<td style="text-align: center"><span style="color: #000080"><strong>Profile</strong></span></td>
<td style="text-align: center"><span style="color: #000080"><strong>Debug Pod(SecurityContext)</strong></span></td>
<td style="text-align: center"><span style="color: #000080"><strong>Debug Node(SecurityContext)</strong></span></td>
</tr>
<tr>
<td style="padding-left: 40px">general</td>
<td style="padding-left: 40px">Add SYS_PTRACE cap</td>
<td style="padding-left: 40px">Attach host &ldquo;/&rdquo; filesystem.
<p>No SecurityContext</p>
<p>Use HostNetwork, HostPID Namespace,HostIPC Namespace</p></td>
</tr>
<tr style="padding-left: 40px">
<td style="padding-left: 40px">baseline</td>
<td style="padding-left: 40px">No SecurityContext</td>
<td style="padding-left: 40px">No SecurityContext</td>
</tr>
<tr style="padding-left: 40px">
<td style="padding-left: 40px">restricted</td>
<td style="padding-left: 40px">Drop ALL capabilities
<p>runAsNonRoot: true</p>
<p>allowPrivilegeEscalation: false</p>
<p>seCompProfile: RuntimeDefault</p></td>
<td style="padding-left: 40px">Drop ALL capabilities
<p>runAsNonRoot: true</p>
<p>allowPrivilegeEscalation: false</p>
<p>seCompProfile: RuntimeDefault</p></td>
</tr>
<tr style="padding-left: 40px">
<td style="padding-left: 40px">netadmin</td>
<td style="padding-left: 40px">Add NET_ADMIN, NET_RAW Cap</td>
<td style="padding-left: 40px">Add NET_ADMIN, NET_RAW Cap
<p>Use HostNetwork, HostPID Namespace,HostIPC Namespace</p></td>
</tr>
<tr style="padding-left: 40px">
<td style="padding-left: 40px">sysadmin</td>
<td style="padding-left: 40px">privileged: true
<p>Use HostNetwork, HostPID Namespace,HostIPC Namespace</p></td>
<td style="padding-left: 40px">privileged: true
<p>Use HostNetwork, HostPID Namespace,HostIPC Namespace</p>
<p>Attach host &ldquo;/&rdquo; filesystem.</p></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">The pod&rsquo;s execution behavior depends on the profile chosen when running the </span><span style="font-weight: 400">kubectl debug</span><span style="font-weight: 400"> command.</span></p>
<h2><span style="font-weight: 400">Caveats with Ephemeral Containers</span><a class="anchor-link" id="caveats-with-ephemeral-containers"></a></h2>
<p><span style="font-weight: 400">Even though ephemeral containers are useful, there are several caveats and potential security risks that users should be aware of:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Ephemeral containers may be able to inspect processes, network traffic, environment variables, mounted volumes, or service account context depending on the Pod and cluster configuration.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">They can bypass the security benefits of distroless or minimal images.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Ephemeral containers can expose secrets</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Once an ephemeral container is added, it remains part of the Pod spec; it is not possible to remove it.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">As long as the ephemeral container&rsquo;s main process is running, you can attach to it using </span><span style="font-weight: 400">kubectl attach</span><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Ephemeral containers behaviour is unpredictable, containers might terminate abruptly depending on the resource configuration and utilization of the pod.</span></li>
</ol>
<h2><span style="font-weight: 400">Guardrails and Best practices with Ephemeral Containers</span><a class="anchor-link" id="guardrails-and-best-practices-with-ephemeral-containers"></a></h2>
<p><span style="font-weight: 400">Ephemeral containers are powerful, but they can create operational and security risks if used carelessly. Adhering to the following guardrails and best practices can help mitigate these risks:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Control ephemeral containers access through RBAC. Only necessary users should have the privilege.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Avoid using the <code></code></span><span style="font-weight: 400"><span style="color: #0000ff">sysadmin</span></span><span style="font-weight: 400"> profile when possible. The </span><span style="font-weight: 400">restricted</span><span style="font-weight: 400"> profile is better suited for maintaining a strong security posture.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Always use approved, scanned images that contain only the tools required for debugging, rather than generic images like </span><span style="font-weight: 400">ubuntu</span><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Avoid running debug containers with long-running processes like <code><span style="color: #0000ff">sleep infinity</span></code> or <code><span style="color: #0000ff">bash</span></code>. Instead, run specific tools or commands that perform the required action and then terminate.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Audit ephemeral containers usage.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Recycle Pods that contain ephemeral containers whenever possible (e.g., during maintenance windows), especially if the ephemeral process has not terminated.</span></li>
</ol>
<h2><span style="font-weight: 400">Conclusion</span><a class="anchor-link" id="conclusion"></a></h2>
<p><span style="font-weight: 400">Ephemeral containers are a powerful troubleshooting tool for Kubernetes workloads, especially when application images are minimal, distroless, or missing debugging utilities. They allow engineers to inspect a running Pod without rebuilding the image or restarting the workload.</span></p>
<p><span style="font-weight: 400">However, they should be treated as controlled operational access, not as a default debugging shortcut. Ephemeral containers can expose sensitive runtime details such as processes, environment variables, mounted volumes, and network state.</span></p>
<p><span style="font-weight: 400">Always restrict usage with proper RBAC. Use approved debug images, prefer the least-privileged debug profile, and reserve powerful profiles such as </span><span style="font-weight: 400">sysadmin</span><span style="font-weight: 400"> for &ldquo;break-glass&rdquo; scenarios only.</span></p>
<p><span style="font-weight: 400">Debug sessions should be short-lived, intentional, and tied to a real troubleshooting need. In short, ephemeral containers improve debuggability, but they must be used with clear security, audit, and operational guardrails.</span></p>
<p>The post <a href="https://www.percona.com/blog/debugging-with-ephemeral-containers/">Debugging with Ephemeral Containers</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/debugging-with-ephemeral-containers/">Debugging with Ephemeral Containers</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Why I haven’t run my databases on Kubernetes</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/why-i-havent-run-my-databases-on-kubernetes/" />
      <id>https://www.percona.com/blog/why-i-havent-run-my-databases-on-kubernetes/</id>
      <updated>2026-06-30T12:48:45+00:00</updated>
      <author><name>Chetan Shivashankar</name></author>
      <summary type="html"><![CDATA[<p>A few years ago, if there was a discussion on “Should we run databases on Kubernetes?”, there were more people saying no than yes. One of the common answers was, “No. Kubernetes is for stateless workloads. Keep your databases outside.” Thankfully, today the discussion is no longer about whether we should run databases on Kubernetes, … Continued<br />
The post Why I haven’t run my databases on Kubernetes appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/why-i-havent-run-my-databases-on-kubernetes/">Why I haven’t run my databases on Kubernetes</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">A few years ago, if there was a discussion on &ldquo;Should we run databases on Kubernetes?&rdquo;, there were more people saying no than yes. One of the common answers was, &ldquo;No. Kubernetes is for stateless workloads. Keep your databases outside.&rdquo;</span></p>
<p><span style="font-weight: 400">Thankfully, today the discussion is no longer about </span><i><span style="font-weight: 400">whether</span></i><span style="font-weight: 400"> we should run databases on Kubernetes, but </span><i><span style="font-weight: 400">how</span></i><span style="font-weight: 400"> we can run them better on Kubernetes.</span></p>
<p><span style="font-weight: 400">In this post, we will look at some of the common arguments brought up against running databases on Kubernetes.</span></p>
<h2><span style="font-weight: 400">1. Kubernetes was designed for stateless workloads</span><a class="anchor-link" id="1-kubernetes-was-designed-for-stateless-workloads"></a></h2>
<p><span style="font-weight: 400">By far the most common concern</span></p>
<p><span style="font-weight: 400">Even though Kubernetes was initially used mainly for stateless workloads, many additions have been made since its initial version to accommodate stateful workloads. Features like </span><span style="font-weight: 400">StatefulSet</span><span style="font-weight: 400"> and </span><span style="font-weight: 400">PersistentVolumes</span><span style="font-weight: 400"> were introduced, and storage usage has been streamlined through the Container Storage Interface (CSI). Furthermore, the platform itself has evolved to support system-level capabilities like </span><a href="https://kubernetes.io/docs/concepts/cluster-administration/swap-memory-management/"><span style="font-weight: 400">Swap space</span></a><span style="font-weight: 400"> to better manage memory-intensive databases.</span></p>
<p><span style="font-weight: 400">Today, a large number of users are successfully running stateful workloads on K8s. According to the latest CNCF Annual Survey Report, stateful containers have become a standard practice, with </span><a href="https://www.cncf.io/wp-content/uploads/2026/01/CNCF_Annual_Survey_Report_final.pdf"><span style="font-weight: 400">79% of Innovators</span></a><span style="font-weight: 400"> now running stateful applications in production.</span></p>
<h2><span style="font-weight: 400">2. Is my data safe on Kubernetes?</span><a class="anchor-link" id="2-is-my-data-safe-on-kubernetes"></a></h2>
<p><span style="font-weight: 400">Is my data safe on Kubernetes even when pods, nodes, or the entire Kubernetes cluster go down?</span></p>
<p><span style="font-weight: 400">This sounds terrifying at first, but cloud-native architecture is built with failure in mind. Pods die, nodes crash, and entire clusters can fail. The golden rule of running databases on Kubernetes is that your data must never depend on the lifecycle of the compute layer.</span></p>
<p><span style="font-weight: 400">This is where the absolute decoupling of compute and storage becomes critical. The underlying storage infrastructure exists entirely independent of the Pod, Node, and even the Kubernetes cluster itself. As long as your storage backend is architected correctly, your data is preserved regardless of what happens to the compute environment.</span></p>
<p><span style="font-weight: 400">For a database, this tiered isolation changes everything:</span></p>
<ul>
<li style="font-weight: 400"><b>If a Pod or Node dies:</b><span style="font-weight: 400"> Kubernetes automatically schedules a replacement Pod and reattaches it to the existing, intact storage volume.</span></li>
<li style="font-weight: 400"><b>If the entire Kubernetes Cluster goes down:</b><span style="font-weight: 400"> Because the data lives safely outside the cluster boundary, you can spin up a completely new Kubernetes cluster, connect it to the existing storage backend, and restore your database operations.</span></li>
</ul>
<p><span style="font-weight: 400">For replicated databases, a Kubernetes Operator can automate this resilience, detecting failures, promoting replicas, and reconciling your state across nodes, or even helping orchestrate disaster recovery across entirely different clusters.</span></p>
<h2><span style="font-weight: 400">3. Won&rsquo;t My Database Experience Downtime When Pods or Nodes Go Down?</span><a class="anchor-link" id="3-wont-my-database-experience-downtime-when-pods-or-nodes-go-down"></a></h2>
<p><span style="font-weight: 400">What if a Pod or a node running a database goes down? Will the database experience downtime?</span></p>
<p><span style="font-weight: 400">While failover concepts remain similar to traditional environments, Kubernetes transforms this into an automated, declarative process via Operators</span><span style="font-weight: 400">. Kubernetes automatically schedules a replacement Pod and reattaches it to the existing, intact storage volume. If the database is properly configured with a redundant, highly available configuration (recommended configuration), the remaining healthy Pods will continue to serve traffic. When utilizing Kubernetes solutions, you are delivering a data service powered by specific database technology, where high availability is maintained through a cluster of N Pods. The critical shift in mindset here is to focus on the service&rsquo;s resilience rather than the survival of individual Pods; our goal is to optimize the overall service, not the individual Pod, a distinction that is often misunderstood.</span></p>
<h2><span style="font-weight: 400">4. I have already built a lot of custom automation in our in-house environment. Why should I switch to Operators?</span><a class="anchor-link" id="4-i-have-already-built-a-lot-of-custom-automation-in-our-in-house-environment-why-should-i-switch-to-operators"></a></h2>
<p><span style="font-weight: 400">While custom scripts might work well today, proprietary automation always comes with heavy long-term maintenance overhead. To scale effectively, automation should be split into two distinct layers: </span><b>infrastructure</b><span style="font-weight: 400"> and </span><b>database management</b><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">Because Kubernetes is widely adopted and continuously updated by the global community, it handles the infrastructure layer (compute, networking, and storage) out of the box. By adopting a good </span><b>Operator</b><span style="font-weight: 400">, complex database-specific logic is offloaded. Ultimately, using Operators prevents you from reinventing the wheel and lets the teams focus on application value rather than maintaining custom infrastructure code.</span></p>
<h2><span style="font-weight: 400">5. Managing Pods, PVCs and configuration is painful</span><a class="anchor-link" id="5-managing-pods-pvcs-and-configuration-is-painful"></a></h2>
<p><span style="font-weight: 400">Manually managing StatefulSets, PersistentVolumeClaims (PVCs), and various other Kubernetes objects to run a database on Kubernetes can be tedious.</span></p>
<p><span style="font-weight: 400">This is where database operators emerge as game-changers.</span></p>
<p><span style="font-weight: 400">An operator is a Kubernetes-native controller that watches custom resources and continuously works to move the actual system toward the desired state. As a user, you describe something like: </span><i><span style="font-weight: 400">&ldquo;I want a database cluster with three instances, backups enabled, this storage, these resources, this version, monitoring enabled, and this replication setup.&rdquo;</span></i><span style="font-weight: 400"> The operator then handles everything under the hood, including managing Kubernetes objects and implementing the operational logic.</span></p>
<p><span style="font-weight: 400">Instead of playing the role of a mechanic who has to assemble all the parts and make everything run, you simply get into a car built by expert mechanics and drive it. The operator abstracts away the complexity so you can focus on the outcome rather than the implementation details.</span></p>
<h2><span style="font-weight: 400">6. I can configure bare-metal or VM nodes exactly how I want for database workloads, but Kubernetes makes this level of customization impossible.</span><a class="anchor-link" id="6-i-can-configure-bare-metal-or-vm-nodes-exactly-how-i-want-for-database-workloads-but-kubernetes-makes-this-level-of-customization-impossible"></a></h2>
<p class="isSelectedEnd">A very valid concern, especially for database engineers accustomed to carefully tuned virtual machines or bare-metal servers. Running databases inside Pods does introduce an additional abstraction layer. There may be a very small category of specialized, legacy &ldquo;pet&rdquo; databases that require highly specific bare-metal tuning and extreme isolation.</p>
<p class="isSelectedEnd">That said, the vast majority of configurations are achievable on Kubernetes. There are a few exceptions; for example, certain low-level storage tuning options may be limited by CSI driver abstractions. However, these are edge cases that rarely affect modern production deployments and do not impact the vast majority of database workloads.</p>
<p>For almost all modern production use cases, Kubernetes is more than capable of running databases reliably and efficiently.</p>
<h2><span style="font-weight: 400">7. Managed databases are way better than running on kubernetes</span><a class="anchor-link" id="7-managed-databases-are-way-better-than-running-on-kubernetes"></a></h2>
<p><span style="font-weight: 400">Managed databases are a great fit for many use cases, as they remove significant complexity and operational overhead.</span></p>
<p><span style="font-weight: 400">However, they do come with a few caveats:</span></p>
<ul>
<li style="font-weight: 400"><b>Cost at Scale:</b><span style="font-weight: 400"> Managed databases can become incredibly expensive compared to running databases yourself using Kubernetes Operators, especially as your data scales.</span></li>
<li style="font-weight: 400"><b>Vendor Lock-in:</b><span style="font-weight: 400"> When you rely on a cloud provider&rsquo;s managed service, you are typically locked into their ecosystem, making it difficult and costly to migrate away.</span></li>
<li style="font-weight: 400"><b>Configuration Limits:</b><span style="font-weight: 400"> In many cases, cloud vendors restrict your control, making it impossible to apply deep custom configurations or install specific database extensions that your application might require.</span></li>
</ul>
<p><span style="font-weight: 400">In short, many of the concerns raised are no longer relevant today or do not apply to the majority of use cases.</span></p>
<h2><span style="font-weight: 400">Conclusion</span><a class="anchor-link" id="conclusion"></a></h2>
<p><span style="font-weight: 400">It&rsquo;s safe to say that databases run and run well on Kubernetes if configured well. In many cases, the shift is cultural rather than technical.</span></p>
<p><span style="font-weight: 400">Running databases on Kubernetes offers the advantage of preventing vendor lock-in. Even though database migration tools exist, cloud migrations remain difficult in practice. This is especially true with major cloud vendors, where your data layer is often tightly integrated with a complex web-native ecosystem of services.</span></p>
<p><span style="font-weight: 400">Operators excel at automating complex Day-2 operations like failovers, backups, and rolling updates. Running both your application and data layers under a single Kubernetes cluster provides a unified control plane for automation. This setup seamlessly aligns with GitOps workflows and becomes incredibly efficient when managing a large fleet of databases at scale.</span></p>
<p><span style="font-weight: 400">We have seen users derive incredible value by running databases on Kubernetes with operators. Some use it to manage standard database instances, while others have built their own fully fledged, internal DBaaS (Database-as-a-Service) platforms, and the list is long. Selecting the right Operator makes all the difference on this journey. Opting for free and open-source operators like the </span><a href="https://docs.percona.com/percona-operators/"><span style="font-weight: 400">Percona Operators</span></a><span style="font-weight: 400"> not only provides enterprise-grade databases but also saves your teams significant time and money.</span></p>
<p>&nbsp;</p>
<p>The post <a href="https://www.percona.com/blog/why-i-havent-run-my-databases-on-kubernetes/">Why I haven&rsquo;t run my databases on Kubernetes</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/why-i-havent-run-my-databases-on-kubernetes/">Why I haven’t run my databases on Kubernetes</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Building Smart Semantic Search using PostgreSQL and pgvector. Part 3 &#8211; Hybrid Search, Percona Blog, and Widget Improvements</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/06/30/semantic-search-on-postgresql-part-3/" />
      <id>https://percona.community/blog/2026/06/30/semantic-search-on-postgresql-part-3/</id>
      <updated>2026-06-30T11:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>Here I write about what I changed after the first launch: hybrid search, filters and counts on the search page, the widget layout, indexer hardening, and the Percona Blog in the index. People often type short words or names, and pure vector search is weak at that. When I turned search on about a month ago I asked for feedback in Part 1, and most of what follows came from that.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/06/30/semantic-search-on-postgresql-part-3/">Building Smart Semantic Search using PostgreSQL and pgvector. Part 3 &#8211; Hybrid Search, Percona Blog, and Widget Improvements</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Here I write about what I changed after the first launch: hybrid search, filters and counts on the search page, the widget layout, indexer hardening, and the Percona Blog in the index. People often type short words or names, and pure vector search is weak at that. When I turned search on about a month ago I asked for feedback in Part 1, and most of what follows came from that.</p>
<p><a href="https://percona.community/blog/2026/05/29/semantic-search-on-postgresql-part-1/">Part 1</a> is the introduction and stack overview. <a href="https://percona.community/blog/2026/05/31/semantic-search-on-postgresql-part-2/">Part 2</a> is Postgres, chunks, SQL, and the indexer.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/search-part-3-search-page-tabs-pgbackuprest.jpg" alt="Search page with pgbackrest, filter tabs and match quality slider"></figure>
</p>
<h2 id="a-month-in-production">A month in production<a class="anchor-link" id="a-month-in-production"></a></h2>
<p>The API and indexer run in Docker on EC2. The engine is PostgreSQL with <a href="https://github.com/pgvector/pgvector" target="_blank" rel="noopener noreferrer">pgvector</a>. On the site there is a search widget in the blog header and a full results page at <a href="https://percona.community/search/" target="_blank" rel="noopener noreferrer">percona.community/search/</a>. Both call the same API. Search has been up since launch. I log queries for debugging and picked up feedback from colleagues, mostly in conversation and from my own tests, not from an analytics dashboard.</p>
<p>I keep <code>search_history</code> in Postgres as an engineering log: timings, regressions after hybrid changes, vector vs keyword splits. To be honest, the first month is mostly my smoke tests. Lots of <code>test</code>, the same names repeated while I debugged person-search, random widget checks. A &ldquo;top user queries&rdquo; chart from that log would look like internal QA, not real audience insight, so I am not publishing one here.</p>
<p>From feedback and from what I tried by hand, three kinds of queries kept showing up.</p>
<p>Short ones are a single product token (<code>pgbackrest</code>, <code>timescaledb</code>) or a person&rsquo;s name (contributor or Percona Blog author). Long phrases like &ldquo;zero downtime database migration&rdquo; or &ldquo;replication lag troubleshooting&rdquo; still work fine with vector-only search, as in parts 1 and 2. People also want filters by content type and honest counts on the tabs, so it does not feel broken when the UI shows 30 cards but the tab says 900 matched.</p>
<p>That shaped what I worked on in June. The table below is illustrative, not a leaderboard from production logs.</p>
<table>
<thead>
<tr>
<th>Example query</th>
<th>Type</th>
<th>Why it matters</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>timescaledb</code></td>
<td>keyword</td>
<td>one word, weak vector, needs text</td>
</tr>
<tr>
<td><code>PMM</code></td>
<td>keyword</td>
<td>short product name</td>
</tr>
<tr>
<td><code>Peter Zaitsev</code></td>
<td>person</td>
<td>contributor profile plus author articles</td>
</tr>
<tr>
<td><code>slow queries mysql</code></td>
<td>hybrid</td>
<td>short tech phrase, not pure semantics</td>
</tr>
<tr>
<td><code>zero downtime database migration</code></td>
<td>semantic</td>
<td>long query as designed</td>
</tr>
</tbody>
</table>
<p>In <code>/demo</code> under History I left the log and the Word statistics tab for myself. When traffic grows and I filter out test noise, that view will be more useful.</p>
<h2 id="widget-and-the-search-page">Widget and the search page<a class="anchor-link" id="widget-and-the-search-page"></a></h2>
<p>The widget has two modes, the popup in the header and the full <code>/search/</code> page (see the screenshot at the top of the post for tabs, counts, and the match quality slider).</p>
<p>From feedback I added filter tabs with multi-select (All, Blog, Percona Blog, Events, Talks, Contributors). The choice goes into the URL as <code>?type=blog,talk</code>. Tabs show counts in parentheses. Without a query that is indexed totals from public <code>GET /health</code>. After a search it is match counts per type from a separate <code>COUNT</code> in the API, without loading every card. There is a &ldquo;Minimum match quality&rdquo; slider for <code>min_score</code>, saved in <code>localStorage</code> and the URL. Search statistics (timings, vector vs keyword, score range) sit behind a compact link instead of taking half the page. Cards on <code>/search/</code> have a preview image, type, score, and a shorter excerpt.</p>
<p>I set the search page content width to 960px with tabs and controls centered. Small thing, but it reads better on mobile and desktop.</p>
<p>The next two screenshots use <code>Peter Zaitsev</code> as the person-search demo. <code>-pz-</code> in the filenames is shorthand for that query.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/search-part-3-search-stat-tabs-pz.jpg" alt="Search statistics for Peter Zaitsev, timings, hybrid split, tab match counts"></figure>
</p>
<p>The popup in the site header uses the same API. Metadata stays compact above the results.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/search-part-3-widget-pz-top.jpg" alt="Header popup, Peter Zaitsev, contributor profile first"></figure>
</p>
<h2 id="why-short-queries-hurt-pure-vector-search">Why short queries hurt pure vector search<a class="anchor-link" id="why-short-queries-hurt-pure-vector-search"></a></h2>
<p>The embedding model is trained on phrases and context. A one-token query like <code>pgbackrest</code> or two words without a clear topic like <code>Peter Zaitsev</code> gives a short vector with a weak signal. In 768 dimensions many irrelevant chunks still land &ldquo;not too far away&rdquo;, especially across 18k chunks.</p>
<p>With <code>Peter Zaitsev</code>, vector search pulled random old posts that mention the name in the body. The contributor profile and recent author articles were not on top. With <code>pgbackrest</code>, semantics blurred and the top hits were &ldquo;something about backup&rdquo;, not necessarily pgBackRest.</p>
<p>A long query like &ldquo;how to reduce replication lag on PostgreSQL&rdquo; is a different story. Query and documents are rich in context and cosine similarity behaves predictably. For that I kept vector-only.</p>
<h2 id="hybrid-search-options">Hybrid search options<a class="anchor-link" id="hybrid-search-options"></a></h2>
<p>I needed a stronger keyword leg for short queries without breaking semantic search for long ones.</p>
<table>
<thead>
<tr>
<th>Option</th>
<th>Pros</th>
<th>Cons / why not now</th>
</tr>
</thead>
<tbody>
<tr>
<td>PostgreSQL FTS (<code>to_tsvector</code>, <code>ts_rank</code>)</td>
<td>built-in, GIN indexes</td>
<td>dictionaries, stemming for names and brands</td>
</tr>
<tr>
<td><code>pg_trgm</code></td>
<td>good for typos</td>
<td>heavier at scale, extra indexes</td>
</tr>
<tr>
<td>OpenSearch / Elasticsearch</td>
<td>mature BM25</td>
<td>another cluster, I skipped this in Part 1</td>
</tr>
<tr>
<td>ILIKE plus heuristic score</td>
<td>fast to ship, one Postgres, easy to debug</td>
<td>not full BM25, <code>%pattern%</code> without GIN slows down at huge scale</td>
</tr>
</tbody>
</table>
<p>I started with ILIKE as step one. Something to compare against, with a clear upgrade path to FTS, trigram, or RRF. At community scale, about 7k documents, it is acceptable for now.</p>
<h2 id="how-hybrid-search-works-in-the-api">How hybrid search works in the API<a class="anchor-link" id="how-hybrid-search-works-in-the-api"></a></h2>
<h3 id="query-mode">Query mode<a class="anchor-link" id="query-mode"></a></h3>
<p><code>detect_search_mode()</code> in <code>search_hybrid.py</code> picks one of three modes.</p>
<ul>
<li><code>keyword</code> for one token (<code>timescaledb</code>, <code>audit_log</code>)</li>
<li><code>person</code> for 2-4 name-like tokens (<code>Peter Zaitsev</code>), with stop words like <code>percona</code>, <code>mysql</code></li>
<li><code>semantic</code> for everything else, vector only</li>
</ul>
<h3 id="two-search-legs">Two search legs<a class="anchor-link" id="two-search-legs"></a></h3>
<p>For <code>keyword</code> and <code>person</code> I run vector search as before (best chunk per document, <code>score &gt;= min_score</code>, <code>LIMIT N</code>) and keyword SQL on <code>pages</code>, plus a chunk body check when needed:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">sql</span><button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-0">
<div class="highlight">
<pre class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">WHERE</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">author</span><span class="w"> </span><span class="k">ILIKE</span><span class="w"> </span><span class="s1">'%Peter Zaitsev%'</span><span class="w"> </span><span class="k">ESCAPE</span><span class="w"> </span><span class="s1">''</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">OR</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">title</span><span class="w"> </span><span class="k">ILIKE</span><span class="w"> </span><span class="s1">'%Peter Zaitsev%'</span><span class="w"> </span><span class="k">ESCAPE</span><span class="w"> </span><span class="s1">''</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">OR</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">description</span><span class="w"> </span><span class="k">ILIKE</span><span class="w"> </span><span class="s1">'%Peter Zaitsev%'</span><span class="w"> </span><span class="k">ESCAPE</span><span class="w"> </span><span class="s1">''</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">OR</span><span class="w"> </span><span class="err">&hellip;</span><span class="w"> </span><span class="n">tags</span><span class="p">,</span><span class="w"> </span><span class="n">chunk</span><span class="w"> </span><span class="n">body</span><span class="w"> </span><span class="err">&hellip;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">CASE</span><span class="w"> </span><span class="k">WHEN</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">author</span><span class="w"> </span><span class="k">ILIKE</span><span class="w"> </span><span class="err">&hellip;</span><span class="w"> </span><span class="k">THEN</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">WHEN</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">title</span><span class="w"> </span><span class="k">ILIKE</span><span class="w"> </span><span class="err">&hellip;</span><span class="w"> </span><span class="k">THEN</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">ELSE</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="k">END</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="nb">date</span><span class="w"> </span><span class="k">DESC</span><span class="w"> </span><span class="n">NULLS</span><span class="w"> </span><span class="k">LAST</span></span></span></code></pre>
</div>
</div>
</div>
<p>For <code>person</code> I add ILIKE on each name part in author and title. Heuristic score 0.72-0.99 in Python (<code>score_keyword_row</code>). Exact author match ranks above a mention in the body.</p>
<p>ILIKE and user input. The SQL above uses literals for readability. In code every pattern is <code>ILIKE %s ESCAPE '\'</code> and psycopg2 binds the value. The query string is never concatenated into SQL, so classic injection like <code>' OR 1=1 --</code> does not apply. Parameters alone are not enough for ILIKE because <code>%</code> and <code>_</code> are wildcards inside the pattern. I escape <code></code>, <code>%</code>, and <code>_</code> in Python, wrap the term in <code>%&hellip;%</code>, and set <code>ESCAPE '\'</code> in SQL so a user cannot widen the match with their own <code>%</code>. Chunk body checks use <code>POSITION(LOWER(%s) IN LOWER(chunk_text))</code> with the same bound parameter.</p>
<h3 id="merge">Merge<a class="anchor-link" id="merge"></a></h3>
<p><code>merge_search_results()</code> unions by <code>slug</code>. If both legs match the same document, I keep the higher score. Then sort by score, then recency for dated content. For <code>person</code>, the matching contributor profile goes first.</p>
<p>The user&rsquo;s <code>min_score</code> is applied after merge so weak vector-only hits do not slip through when the threshold is raised.</p>
<h3 id="tab-counts">Tab counts<a class="anchor-link" id="tab-counts"></a></h3>
<p>For badges like <code>Percona Blog (905)</code> the API runs <code>COUNT(DISTINCT slug) &hellip; GROUP BY content_type</code> with the same keyword conditions, no row <code>LIMIT</code>. The UI still shows 30 best cards. The tab number is how many matched in total.</p>
<p>The response includes <code>stats.search_mode</code> (<code>semantic</code>, <code>keyword</code>, or <code>person</code>) and timings split into <code>vector_db_ms</code> and <code>keyword_db_ms</code>.</p>
<pre class="mermaid">
flowchart LR
Q["Query"] --&gt; D{detect_search_mode}
D --&gt;|semantic| V["vector_search"]
D --&gt;|keyword / person| V
D --&gt;|keyword / person| K["keyword_search ILIKE"]
V --&gt; M["merge by slug"]
K --&gt; M
M --&gt; F["filter min_score"]
F --&gt; R["&le; limit cards"]
K --&gt; C["COUNT for tabs"]
</pre>
<h2 id="the-bug-that-broke-person-search-in-production">The bug that broke person search in production<a class="anchor-link" id="the-bug-that-broke-person-search-in-production"></a></h2>
<p>After I shipped hybrid search, queries like <code>Peter Zaitsev</code> returned 500. The widget showed &ldquo;Oops, sorry &ndash; something went wrong on our end.&rdquo; Semantic queries still worked.</p>
<p>API logs:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">text</span><button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-2">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">psycopg2.errors.InternalError_: could not load library "/usr/pgsql-18/lib/llvmjit.so":
</span></span><span class="line"><span class="cl">undefined symbol: _ZSt21__glibcxx_assert_failPKciS0_S0_</span></span></code></pre>
</div>
</div>
</div>
<p>Hybrid person search builds a heavy plan with <code>ILIKE</code> and <code>POSITION(LOWER(...) IN chunk_text)</code> over 18k chunks. PostgreSQL tried to JIT-compile it and failed loading <code>llvmjit.so</code>. The same error is described on the <a href="https://forums.percona.com/t/llvmjit-so-fails-to-load-in-percona-postgresql-17-container/40690" target="_blank" rel="noopener noreferrer">Percona forum</a>. I set <code>jit = off</code> on every API connection and search worked again.</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">sql</span><button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-3">
<div class="highlight">
<pre class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SET</span><span class="w"> </span><span class="n">jit</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">off</span><span class="p">;</span><span class="w"> </span><span class="c1">-- on every API connection</span></span></span></code></pre>
</div>
</div>
</div>
<p>For tab <code>COUNT</code> in person mode I count on <code>pages</code> without scanning chunks. Author and title cover name search. Full keyword search with body still runs with <code>jit = off</code>.</p>
<p>On PG 18 with pgvector and text subqueries, test hybrid on person and keyword queries, not only long phrases. The planner behaves differently.</p>
<h2 id="percona-blog-in-one-index">Percona Blog in one index<a class="anchor-link" id="percona-blog-in-one-index"></a></h2>
<p>The community site already had blog posts, events, talks, and contributors. Without the <a href="https://www.percona.com/blog/" target="_blank" rel="noopener noreferrer">official Percona Blog</a> search felt incomplete. Talks and community posts link there every day.</p>
<h3 id="what-i-had-to-build">What I had to build<a class="anchor-link" id="what-i-had-to-build"></a></h3>
<ol>
<li>New <code>content_type</code>: <code>percona_blog</code>, its own RSS feed at <code>https://www.percona.com/blog/feed/</code>, separate crawler rules.</li>
<li>Same pipeline as the rest: RSS, HTML, chunks, embedding, <code>pages</code> and <code>community_nomic</code>. Same model <code>nomic-embed-text-v1</code>, prefixes <code>search_document:</code> and <code>search_query:</code> unchanged.</li>
<li>WordPress and RSS quirks on Percona Blog. Images in Open Graph, author in metadata, full text only on the HTML page. I added <code>fetch_percona_blog_image()</code> and a <code>percona_blog</code> branch in <code>crawler.py</code>.</li>
<li>Scale. Roughly 6200+ new documents. The index went from about 800 to about 7000 pages and 18,000 chunks. HNSW on a single Postgres on EC2 still copes, but a full re-index takes hours, not minutes.</li>
</ol>
<h3 id="indexer-after-crashes">Indexer after crashes<a class="anchor-link" id="indexer-after-crashes"></a></h3>
<p>The first full Percona Blog crawl failed several times. Worker OOM, Docker restarts, a long RSS walk. I hardened the indexer:</p>
<table>
<thead>
<tr>
<th>Problem</th>
<th>Fix</th>
</tr>
</thead>
<tbody>
<tr>
<td>Re-crawl from scratch after a crash</td>
<td><code>skip_known</code>, skip URLs already in <code>pages</code></td>
</tr>
<tr>
<td>Resume mid-RSS</td>
<td>start feed page near <code>indexed_count // 10</code></td>
</tr>
<tr>
<td>Cancel did not work</td>
<td><code>cancel_requested</code> checks between RSS pages, before fetch and encode</td>
</tr>
<tr>
<td>OOM during embedding</td>
<td><code>EMBED_BATCH_SIZE=8</code>, batched <code>model.encode()</code></td>
</tr>
<tr>
<td>No visibility</td>
<td><code>progress_log</code> on <code>indexer_runs</code>, live log in demo</td>
</tr>
<tr>
<td>Stuck task after kill</td>
<td>on worker startup honour cancel, re-queue resume tasks</td>
</tr>
</tbody>
</table>
<p>Same worker and <code>index_queue</code>, but behavior closer to production ETL than a one-off script.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/search-part-3-sources.jpg" alt="Admin dashboard, documents, chunks, and percona_blog in the index"></figure>
</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/search-part-3-index.jpg" alt="Percona Blog indexing, progress log and current URL"></figure>
</p>
<h2 id="whats-next">What&rsquo;s next<a class="anchor-link" id="whats-next"></a></h2>
<ul>
<li>PostgreSQL FTS instead of bare ILIKE for the keyword leg</li>
<li>Pagination on <code>/search/</code> if I need to go past 30. Nobody needs all 900 author posts on one screen, &ldquo;load 30 more&rdquo; is enough</li>
<li>More sources from the Part 1 roadmap: video, GitHub, forum</li>
</ul>
<h2 id="try-it-yourself">Try it yourself<a class="anchor-link" id="try-it-yourself"></a></h2>
<p>Popup search is in the <a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community</a> header. Full results are at <a href="https://percona.community/search/?q=zero+downtime+database+migration" target="_blank" rel="noopener noreferrer">percona.community/search/</a>.</p>
<p>Examples to try. These are demos of different modes, not a top from the log.</p>
<table>
<thead>
<tr>
<th>Query</th>
<th>Mode</th>
<th>What to check</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>zero downtime database migration</code></td>
<td>semantic</td>
<td>long phrase, vector-only</td>
</tr>
<tr>
<td><code>replication lag troubleshooting</code></td>
<td>semantic</td>
<td>same</td>
</tr>
<tr>
<td><code>pgbackrest</code></td>
<td>keyword</td>
<td>one-word product, hybrid</td>
</tr>
<tr>
<td><code>Peter Zaitsev</code></td>
<td>person</td>
<td>contributor plus author articles</td>
</tr>
<tr>
<td><code>best pizza recipe napoli</code></td>
<td>semantic</td>
<td>off-topic, empty above <code>min_score</code></td>
</tr>
</tbody>
</table>
<p>As in <a href="https://percona.community/blog/2026/05/29/semantic-search-on-postgresql-part-1/">Part 1</a>, I am not publishing the search service code. It is built for percona.community. These posts share observations and ideas you can adapt. Vector search schema and SQL are in <a href="https://percona.community/blog/2026/05/31/semantic-search-on-postgresql-part-2/">Part 2</a>. The database is open-source <a href="https://docs.percona.com/postgresql/18/index.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL</a>.</p>
<p>If you are building something similar or hit an edge case, leave a comment. In Part 1 I asked for feedback and it led to this post.</p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/06/30/semantic-search-on-postgresql-part-3/">Building Smart Semantic Search using PostgreSQL and pgvector. Part 3 &#8211; Hybrid Search, Percona Blog, and Widget Improvements</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Skipping Percona Server for MySQL 8.4.9 and 9.7.0</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/percona-server-mysql-8-4-9-9-7-0-skipped/" />
      <id>https://www.percona.com/blog/percona-server-mysql-8-4-9-9-7-0-skipped/</id>
      <updated>2026-06-29T15:19:11+00:00</updated>
      <author><name>Dennis Kittrell</name></author>
      <summary type="html"><![CDATA[<p>Upstream MySQL published an out-of-schedule release this week with two high-severity CVE fixes. We’ve pulled those fixes into our next builds and are skipping the two versions we had already queued: Percona Server for MySQL 8.4.9 and 9.7.0. These fixes arrived through Oracle’s new monthly Critical Security Patch Updates (CSPUs), which Oracle announced begin May … Continued<br />
The post Skipping Percona Server for MySQL 8.4.9 and 9.7.0 appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/percona-server-mysql-8-4-9-9-7-0-skipped/">Skipping Percona Server for MySQL 8.4.9 and 9.7.0</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Upstream MySQL published an out-of-schedule release this week with two high-severity CVE fixes. We&rsquo;ve pulled those fixes into our next builds and are skipping the two versions we had already queued: Percona Server for MySQL 8.4.9 and 9.7.0.</p>
<p>These fixes arrived through Oracle&rsquo;s new monthly Critical Security Patch Updates (CSPUs), which <a href="https://blogs.oracle.com/security/update-monthly-critical-security-patch-updates-cspus-begin-may-28-2026" target="_blank" rel="noopener">Oracle announced begin May 28, 2026</a>. CSPUs ship targeted high-severity fixes between Oracle&rsquo;s quarterly Critical Patch Updates. For MySQL, these updates are issued as needed rather than on a fixed monthly schedule, so out-of-schedule security fixes like these may become more common.</p>
<p>We&rsquo;ve handled a skip like this before. When MySQL Community Server 8.4.2 followed 8.4.1 by only a few weeks, we skipped 8.4.1 and shipped its contents in 8.4.2-2. This is the same approach.</p>
<h2>What&rsquo;s happening<a class="anchor-link" id="whats-happening"></a></h2>
<p>The code for 8.4.9 and 9.7.0 was already ready for packaging when the CVE fixes landed. Rather than ship those builds and follow immediately with a security patch, we applied the fixes, re-tested, and re-tagged. Percona Server for MySQL 8.4.10 and 9.7.1 will carry everything 8.4.9 and 9.7.0 would have contained, plus the upstream high-severity CVE fixes.</p>
<p>These fixes come from Oracle&rsquo;s <a href="https://www.oracle.com/security-alerts/cspujun2026.html" target="_blank" rel="noopener">June 2026 Critical Security Patch Update</a>; the specific CVE identifiers will be listed in the 8.4.10 and 9.7.1 release notes. No action is required on your part. The fixes reach you in 8.4.10 and 9.7.1, expected within days. If your security policy requires faster remediation, contact Percona Support to discuss interim options.</p>
<p>8.4.9 and 9.7.0 will not appear in the package repositories. A normal upgrade moves you straight to 8.4.10 or 9.7.1, which carry the skipped versions&rsquo; content.</p>
<h2>Who this affects<a class="anchor-link" id="who-this-affects"></a></h2>
<p>If you were waiting specifically for 8.4.9 or 9.7.0, those versions won&rsquo;t be published. Point your upgrade at the next releases instead, which include the same content and the CVE fixes. The delay is a few days, not weeks. If you weren&rsquo;t tracking a specific version number, nothing changes for you.</p>
<h2>What to do<a class="anchor-link" id="what-to-do"></a></h2>
<p>Nothing urgent. Upgrade to the next Percona Server for MySQL releases as you normally would once they&rsquo;re published. We&rsquo;ll announce them through release notes and the Percona Blog. For questions about timing or the security content, reach out to Percona Support or post in the Percona Community Forum.</p>
<h2>What to expect going forward<a class="anchor-link" id="what-to-expect-going-forward"></a></h2>
<p>Oracle&rsquo;s monthly CSPUs mean out-of-schedule fixes will happen more often. Our approach stays consistent: we evaluate every upstream release, and when high-severity fixes land between our scheduled releases, we fold them into the next release rather than shipping a separate build for each one. Your LTS support commitments don&rsquo;t change. We&rsquo;re watching how often Oracle uses the monthly cadence and will adjust release planning if the volume warrants it.</p>
<p><!-- notionvc: df2d4121-e0ee-4824-bc7f-3c4c0773142e --></p>
<p>The post <a href="https://www.percona.com/blog/percona-server-mysql-8-4-9-9-7-0-skipped/">Skipping Percona Server for MySQL 8.4.9 and 9.7.0</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/percona-server-mysql-8-4-9-9-7-0-skipped/">Skipping Percona Server for MySQL 8.4.9 and 9.7.0</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Foundation Sea Lion Champions Nominees: Fariha Shaikh</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-fariha-shaikh/" />
      <id>https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-fariha-shaikh/</id>
      <updated>2026-06-29T04:43:34+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>The MariaDB Foundation Sea Lion Champions program celebrates the people and organizations who help make the MariaDB ecosystem stronger, more open, and more useful for everyone. …<br />
Continue reading \"MariaDB Foundation Sea Lion Champions Nominees: Fariha Shaikh\"<br />
The post MariaDB Foundation Sea Lion Champions Nominees: Fariha Shaikh appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-fariha-shaikh/">MariaDB Foundation Sea Lion Champions Nominees: Fariha Shaikh</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>The MariaDB Foundation Sea Lion Champions program celebrates the people and organizations who help make the MariaDB ecosystem stronger, more open, and more useful for everyone. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-fariha-shaikh/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Foundation Sea Lion Champions Nominees: Fariha Shaikh&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-fariha-shaikh/">MariaDB Foundation Sea Lion Champions Nominees: Fariha Shaikh</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-fariha-shaikh/">MariaDB Foundation Sea Lion Champions Nominees: Fariha Shaikh</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Enterprise Server Q2 2026 Maintenance Releases</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/mariadb-enterprise-server-q2-2026-maintenance-releases/" />
      <id>https://mariadb.com/resources/blog/mariadb-enterprise-server-q2-2026-maintenance-releases/</id>
      <updated>2026-06-26T22:25:04+00:00</updated>
      <author><name>Daniel Bartholomew</name></author>
      <summary type="html"><![CDATA[<p>New maintenance releases for MariaDB Enterprise Server: 11.8.8-5, 11.4.12-9, and 10.6.27-23 are now available. Download Now Notable Release Updates MariaDB […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-enterprise-server-q2-2026-maintenance-releases/">MariaDB Enterprise Server Q2 2026 Maintenance Releases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>New maintenance releases for MariaDB Enterprise Server: 11.8.8-5, 11.4.12-9, and 10.6.27-23 are now available. Download Now MariaDB Enterprise Server is an enhanced, hardened and secured version of MariaDB Community Server that delivers enterprise reliability, stability and long-term support as well as greater operational efficiency when it comes&hellip;</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-enterprise-server-q2-2026-maintenance-releases/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-enterprise-server-q2-2026-maintenance-releases/">MariaDB Enterprise Server Q2 2026 Maintenance Releases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Privacy-First Stack: Nextcloud, Passbolt and MariaDB Server</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-privacy-first-stack-nextcloud-passbolt-and-mariadb-server/" />
      <id>https://mariadb.org/mariadb-privacy-first-stack-nextcloud-passbolt-and-mariadb-server/</id>
      <updated>2026-06-26T12:37:50+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>I hear this sentence a lot: “We care about privacy.”<br />
Good.<br />
But then you look a bit closer.<br />
Files are on some cloud platform. Nobody is completely sure which settings were changed two years ago. …<br />
Continue reading \"MariaDB Privacy-First Stack: Nextcloud, Passbolt and MariaDB Server\"<br />
The post MariaDB Privacy-First Stack: Nextcloud, Passbolt and MariaDB Server appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-privacy-first-stack-nextcloud-passbolt-and-mariadb-server/">MariaDB Privacy-First Stack: Nextcloud, Passbolt and MariaDB Server</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>I hear this sentence a lot: &ldquo;We care about privacy.&rdquo;<br>
Good.<br>
But then you look a bit closer.<br>
Files are on some cloud platform. Nobody is completely sure which settings were changed two years ago. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-privacy-first-stack-nextcloud-passbolt-and-mariadb-server/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Privacy-First Stack: Nextcloud, Passbolt and MariaDB Server&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-privacy-first-stack-nextcloud-passbolt-and-mariadb-server/">MariaDB Privacy-First Stack: Nextcloud, Passbolt and MariaDB Server</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-privacy-first-stack-nextcloud-passbolt-and-mariadb-server/">MariaDB Privacy-First Stack: Nextcloud, Passbolt and MariaDB Server</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Why PostgreSQL needs an AI usage policy</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/06/26/why-postgresql-needs-an-ai-usage-policy/" />
      <id>https://percona.community/blog/2026/06/26/why-postgresql-needs-an-ai-usage-policy/</id>
      <updated>2026-06-26T10:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>We often hear that open source is about people.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/06/26/why-postgresql-needs-an-ai-usage-policy/">Why PostgreSQL needs an AI usage policy</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>We often hear that open source is about people.</p>
<p>People who contribute their time and, in a way, parts of their lives to work on software that is available for everyone without limitations and without licensing costs.</p>
<p>The more popular a project becomes, the more often we also hear about the need for sustainable open source. Nothing surprising here. Often projects start off as &ldquo;scratching ones itch&rdquo; and it&rsquo;s very appreciated when others notice the work done. The more time passes and the more the work becomes appreciated, the higher the chances that there will be a need to spend more time on the project.</p>
<p>When projects graduate from a hobby project to software used by thousands of users, or even a foundational building block in production, things get interesting.</p>
<p>At that point, we may hope to see new contributors joining the project. This would normally be a good thing. But is it still the same in the AI hype era, where anyone can generate almost any content and claim it as their own?</p>
<p>AI was supposed to be a killer of open source.&nbsp;After all, a lot of publicly available code from open source communities was part of what AI systems trained on.&nbsp;The fear was that as it would become so easy to create our own software, there would not be as much need for the existing open source projects. While this was the hype speaking, we can notice another trend. It became much easier to propose patches, detect and report security threats, or submit code reviews. Even without any developer experience or coding capabilities.</p>
<h3 id="how-sustainable-is-that-for-the-human-maintainers">How sustainable is that for the human maintainers?<a class="anchor-link" id="how-sustainable-is-that-for-the-human-maintainers"></a></h3>
<p>It is easy to imagine that there is a very fine line between positively helpful and overwhelming. As with anything unwanted, AI-generated code or text can be harmful to many open source projects. Especially those with a single maintainer treating their project as a spare time hobby and suddenly experiencing a waterfall of <a href="https://en.wikipedia.org/wiki/AI_slop" target="_blank" rel="noopener noreferrer">AI-slop</a>.</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/06/Jan-ps3.png" alt="RPCS3 plead to vibe coders social media post"></figure>
</p>
<p><a href="https://github.com/RPCS3/rpcs3" target="_blank" rel="noopener noreferrer">Playstation 3 emulator project</a> recently <a href="https://x.com/rpcs3/status/2053248922974605431?lang=en" target="_blank" rel="noopener noreferrer">RPCS3 posted a plea</a> to the vibe coders to stop the AI-generated abuse already and they are not alone in this problem.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/Jan-fosdem-curl.png" alt="FOSDEM 2026 Daniel Stenberg presentation"></figure>
</p>
<p>Daniel Stenberg from the <a href="https://curl.se/" target="_blank" rel="noopener noreferrer">curl project</a> captured this well in <a href="https://fosdem.org/2026/schedule/event/B7YKQ7-oss-in-spite-of-ai/" target="_blank" rel="noopener noreferrer">his FOSDEM 2026 talk</a> summarizing that: &ldquo;AI gives us the worst and the best, simultaneously.&rdquo;</p>
<p>In the same talk, he discussed how curl had to stop its bug bounty program. Curl has also posted on the <a href="https://curl.se/dev/contribute.html#on-ai-use-in-curl" target="_blank" rel="noopener noreferrer">rules of AI use</a>. Even that was not enough, which led to the &ldquo;<a href="https://daniel.haxx.se/blog/2026/06/15/curl-summer-of-bliss/" target="_blank" rel="noopener noreferrer">curl summer of bliss</a>&rdquo;, where they will:</p>
<blockquote>
<p>not accept or otherwise handle any vulnerability reports during the month of July 2026.</p>
</blockquote>
<p>Security reports are an especially sensitive case. An AI-generated vulnerability report is not harmless. Someone has to read it, reproduce it, evaluate it and decide whether it is real. Even when the issue is not there, the work is still very real. Like it or not but when it&rsquo;s unfounded work that proves a ai-generated false it is abusive.</p>
<p>Knowing that some projects adopt AI-focused policies, I searched for examples of such policies, using AI obviously &#128578;, and stumbled upon <a href="https://github.com/melissawm/open-source-ai-contribution-policies" target="_blank" rel="noopener noreferrer">a very useful (open source!) list that already gathers this kind of information</a>.</p>
<p>Further analysis of the resources linked in the list, as of June 2026, shows that most policies allow assisted use, but not &ldquo;AI as the contributor.&rdquo;</p>
<p>Commonly allowed uses include:</p>
<ul>
<li>drafting code,</li>
<li>generating tests,</li>
<li>improving docs,</li>
<li>debugging,</li>
<li>summarizing, or asking an LLM for help.</li>
</ul>
<p>All of that is usually acceptable as long as the human reviews and owns the result.</p>
<p>Typically banned practices include:</p>
<ul>
<li>fully AI-generated PRs with little human engagement</li>
<li>AI-generated &ldquo;good first issue&rdquo; work</li>
<li>AI as co-author</li>
<li>automated AI code reviews</li>
<li>unreviewed agentic output</li>
</ul>
<p>It is completely understandable that experienced developers and communities say &ldquo;no&rdquo; to submissions of low quality. That would not be sustainable. Maintainers already carry a lot of invisible work, and AI can easily multiply that work if contributors treat it as a shortcut instead of a tool.</p>
<p>What is very positive for the future of AI-enhanced work is that the general direction seems to be acceptance, as long as there is a human-in-the-loop.</p>
<p>AI-enhanced work, as long as a human was involved, is possible across a range of open source products: <a href="https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions" target="_blank" rel="noopener noreferrer">Apache Airflow</a>, <a href="https://datafusion.apache.org/contributor-guide/index.html#ai-assisted-contributions" target="_blank" rel="noopener noreferrer">Apache DataFusion</a>, <a href="https://arrow.apache.org/docs/dev/developers/overview.html#ai-generated-code" target="_blank" rel="noopener noreferrer">Arrow</a>, <a href="https://github.com/cloudnative-pg/governance/blob/main/AI_POLICY.md" target="_blank" rel="noopener noreferrer">CloudNativePG (CNPG)</a>, <a href="https://devguide.python.org/getting-started/ai-tools/index.html" target="_blank" rel="noopener noreferrer">CPython</a>, <a href="https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/submitting-patches/#ai-assisted-contributions" target="_blank" rel="noopener noreferrer">Django</a>, <a href="https://firefox-source-docs.mozilla.org/contributing/ai-coding.html" target="_blank" rel="noopener noreferrer">Firefox</a>, <a href="https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines" target="_blank" rel="noopener noreferrer">Flutter</a>, <a href="https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md" target="_blank" rel="noopener noreferrer">Ghostty</a>, <a href="https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#ai-contribution-policy" target="_blank" rel="noopener noreferrer">Gitea</a>, <a href="https://github.com/Homebrew/brew/blob/main/CONTRIBUTING.md#artificial-intelligencelarge-language-model-aillm-usage" target="_blank" rel="noopener noreferrer">Homebrew</a>, <a href="https://www.kubernetes.dev/docs/guide/pull-requests/#ai-guidance" target="_blank" rel="noopener noreferrer">Kubernetes</a>, <a href="https://kernel.org/doc/html/next/process/coding-assistants.html" target="_blank" rel="noopener noreferrer">Linux Kernel</a>, <a href="https://llvm.org/docs//AIToolPolicy.html" target="_blank" rel="noopener noreferrer">LLVM</a>, <a href="https://matplotlib.org/devdocs/devel/contribute.html#generative-ai" target="_blank" rel="noopener noreferrer">Matplotlib</a>, <a href="https://numpy.org/devdocs/dev/ai_policy.html" target="_blank" rel="noopener noreferrer">NumPy</a>, <a href="https://pandas.pydata.org/docs/dev/development/contributing.html#automated-contributions-policy" target="_blank" rel="noopener noreferrer">Pandas</a>, <a href="https://github.com/pytorch/pytorch/blob/main/CONTRIBUTING.md#ai-assisted-development" target="_blank" rel="noopener noreferrer">PyTorch</a>, <a href="https://scipy.github.io/devdocs/dev/conduct/ai_policy.html" target="_blank" rel="noopener noreferrer">SciPy</a>, <a href="https://docs.sympy.org/dev/contributing/ai-generated-code-policy.html" target="_blank" rel="noopener noreferrer">SymPy</a>, <a href="https://docs.wagtail.org/en/latest/contributing/general_guidelines.html#general-coding-guidelines" target="_blank" rel="noopener noreferrer">Wagtail</a>, <a href="https://github.com/zulip/zulip/blob/main/CONTRIBUTING.md#ai-use-policy-and-guidelines" target="_blank" rel="noopener noreferrer">Zulip</a>, and <a href="https://github.com/zulip/zulip/blob/main/CONTRIBUTING.md#ai-use-policy-and-guidelines" target="_blank" rel="noopener noreferrer">others</a>.</p>
<p>What is interesting is that, at this moment, PostgreSQL does not have any official policy of this sort available.</p>
<h3 id="slonik-says-i-havent-noticed">Slonik says &ldquo;I haven&rsquo;t noticed&hellip;&rdquo;<a class="anchor-link" id="slonik-says-i-havent-noticed"></a></h3>
<p>While this may be a problem that does not directly touch PostgreSQL as a database server, it already has an impact on the PostgreSQL ecosystem, which consists of many other extensions and tools.</p>
<p>The reason may be quite trivial. Even with AI, the entry threshold for PostgreSQL core hacking is still higher than for many other tools. Hackers communicate through mailing lists, and even with the adoption of modern tools like Hackorum.dev, it is still not that easy to work with PostgreSQL compared with many other, more tempting projects.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/Jan-waterfall.png" alt="Beware of elephants drowning in AI slop"></figure>
</p>
<p>The issue, as I often see it for PostgreSQL, is that there is not much leadership for the wider ecosystem from the core project. Availability of responsible AI usage policies for the ecosystem could make maintainers&rsquo; lives easier. And let&rsquo;s be honest, for many smaller projects, creating such policies from scratch is a burden they could be spared.</p>
<p>Seems like any help would be appreciated.</p>
<h3 id="what-now-is-this-over-was-this-a-rant">What now? Is this over? Was this a rant?<a class="anchor-link" id="what-now-is-this-over-was-this-a-rant"></a></h3>
<p>I like to say, and repeat myself, that &ldquo;AI usage in open source is all about respect.&rdquo; To me, this is enough to say all that is needed. People need to communicate. This was meant as a start of the discussion.</p>
<p><a href="https://2026.pgconf.eu/" target="_blank" rel="noopener noreferrer">PGConf.EU</a> is coming in October, as well as many smaller meetups this year. There will be lots of space for hallway track discussions and hopefully some outcomes. Not to mention async communication channels. What I hope is that we can leverage all these channels to propose some solutions, experiment, and get better.</p>
<p>Let this be a call to action to help us all be more reasonable and more respectful of other people&rsquo;s time.</p>
<p>With this in mind,</p>
<details>
<summary>check out my original text before I refined it with AI if you want to see how it changed.</summary>
<p>Often we hear how open source is the people. The people who contribute their time, in a way their lives, to produce software available for everyone without limitations. Without licensing cost on the users.</p>
<p>The more a project becomes popular the higher chances we also hear about the need for sustainable open source from it. Nothing surprising here. At first we want others to notice the work we&rsquo;ve done. The more times pass and the work becomes appreciated, the higher chances that there will be a need to spend more time on the project.</p>
<p>When it graduates from a hobby project to software used by thousands of users or even a production founding block things become really interesting. Now we may hope to see new contributors joining the project. This would normally be a good thing but is it the same in the AI hype era where anyone can generate any content and claim it their own?</p>
<p>AI was supposed to be open source killer because it will become so easy to create our own software and not use open source. While this was the hype speaking, we notice another trend. It became way easier to propose patches, detect and report security threats or submit code reviews. Even without any developer experience or any coding capabilities.</p>
<h4 id="how-sustainable-for-the-human-maintainers-is-that">How sustainable for the human maintainers is that?</h4>
<p>It&rsquo;s easy to imagine that there is a very fine line between positively helpful and overwhelming. As with anything unwanted, the AI generated unwanted code or texts can be harmful to the many ope source projects. Especially those with a single maintainer treating their project as a spare time hobby and experiencing a waterfall of <a href="https://en.wikipedia.org/wiki/AI_slop" target="_blank" rel="noopener noreferrer">AI-slop</a>.</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/06/Jan-ps3.png" alt="RPCS3 plead to vibe coders social media post"></figure>
</p>
<p>Playstation 3 emulator RPCS3 posted a plead to the vibe coders to stop the AI-generated abuse already and they are not alone in this problem.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/Jan-fosdem-curl.png" alt="FOSDEM 2026 Daniel Stenberg presentation"></figure>
</p>
<p>As Daniel Stenberg from curl says (check out his talk during FOSDEM 2026) &ldquo;AI gives us the worst and the best &ndash; simultaneously&rdquo;</p>
<p>Seeing that <a href="https://curl.se/" target="_blank" rel="noopener noreferrer">curl</a> had to stop their bug bounty program (as discussed in the talk above) and even this was not enough and ended up in the &ldquo;<a href="https://daniel.haxx.se/blog/2026/06/15/curl-summer-of-bliss/" target="_blank" rel="noopener noreferrer">curl summer of bliss</a>&rdquo; where they will:</p>
<blockquote>
<p>not accept or otherwise handle any vulnerability reports during the month of July 2026.</p>
</blockquote>
<p>Security reports are an especially sensitive case. A wrong AI-generated vulnerability report is not harmless. Someone has to read it, reproduce it, evaluate it and decide whether it is real. Even when the issue is not there, the work is still very real. Like it or not but when it&rsquo;s unfounded work that proves a ai-generated false it is abusive.</p>
<p>Knowing that some projects adopt policies, I searched (using AI obviously &#128578;) for examples of such policies and stumbled upon <a href="https://github.com/melissawm/open-source-ai-contribution-policies" target="_blank" rel="noopener noreferrer">a very useful list that gathers such information already</a>.</p>
<p>Further analysis of the resources linked in the list (state in June 2026) shows that:</p>
<ul>
<li>Most policies allow assisted use, not &ldquo;AI as the contributor.&rdquo;</li>
<li>Commonly allowed uses include drafting code, generating tests, improving docs, debugging, summarizing, or asking an LLM for help, as long as the human reviews and owns the result.</li>
<li>Typically banned practices include:
<ul>
<li>Fully AI-generated PRs with little human engagement</li>
<li>AI-generated &ldquo;good first issue&rdquo; work</li>
<li>AI as co-author</li>
<li>Automated AI code reviews</li>
<li>Unreviewed agentic output</li>
</ul>
</li>
</ul>
<p>It&rsquo;s only understandable that experienced developers and Communities say &ldquo;no&rdquo; to low quality submissions. That would not be sustainable. What is very positive for the future of AI enhanced work is that the repo shows general acceptance as long as there is a &ldquo;human in the loop&rdquo;. AI enhanced work as long as human was involved is possible across a range of open source products: <a href="https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions" target="_blank" rel="noopener noreferrer">Apache Airflow</a>, <a href="https://datafusion.apache.org/contributor-guide/index.html#ai-assisted-contributions" target="_blank" rel="noopener noreferrer">Apache DataFusion</a>, <a href="https://arrow.apache.org/docs/dev/developers/overview.html#ai-generated-code" target="_blank" rel="noopener noreferrer">Arrow</a>, <a href="https://github.com/cloudnative-pg/governance/blob/main/AI_POLICY.md" target="_blank" rel="noopener noreferrer">CloudNativePG (CNPG)</a>, <a href="https://devguide.python.org/getting-started/ai-tools/index.html" target="_blank" rel="noopener noreferrer">CPython</a>, <a href="https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/submitting-patches/#ai-assisted-contributions" target="_blank" rel="noopener noreferrer">Django</a>, <a href="https://firefox-source-docs.mozilla.org/contributing/ai-coding.html" target="_blank" rel="noopener noreferrer">Firefox</a>, <a href="https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines" target="_blank" rel="noopener noreferrer">Flutter</a>, <a href="https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md" target="_blank" rel="noopener noreferrer">Ghostty</a>, <a href="https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#ai-contribution-policy" target="_blank" rel="noopener noreferrer">Gitea</a>, <a href="https://github.com/Homebrew/brew/blob/main/CONTRIBUTING.md#artificial-intelligencelarge-language-model-aillm-usage" target="_blank" rel="noopener noreferrer">Homebrew</a>, <a href="https://www.kubernetes.dev/docs/guide/pull-requests/#ai-guidance" target="_blank" rel="noopener noreferrer">Kubernetes</a>, <a href="https://kernel.org/doc/html/next/process/coding-assistants.html" target="_blank" rel="noopener noreferrer">Linux Kernel</a>, <a href="https://llvm.org/docs//AIToolPolicy.html" target="_blank" rel="noopener noreferrer">LLVM</a>, <a href="https://matplotlib.org/devdocs/devel/contribute.html#generative-ai" target="_blank" rel="noopener noreferrer">Matplotlib</a>, <a href="https://numpy.org/devdocs/dev/ai_policy.html" target="_blank" rel="noopener noreferrer">NumPy</a>, <a href="https://pandas.pydata.org/docs/dev/development/contributing.html#automated-contributions-policy" target="_blank" rel="noopener noreferrer">Pandas</a>, <a href="https://github.com/pytorch/pytorch/blob/main/CONTRIBUTING.md#ai-assisted-development" target="_blank" rel="noopener noreferrer">PyTorch</a>, <a href="https://scipy.github.io/devdocs/dev/conduct/ai_policy.html" target="_blank" rel="noopener noreferrer">SciPy</a>, <a href="https://docs.sympy.org/dev/contributing/ai-generated-code-policy.html" target="_blank" rel="noopener noreferrer">SymPy</a>, <a href="https://docs.wagtail.org/en/latest/contributing/general_guidelines.html#general-coding-guidelines" target="_blank" rel="noopener noreferrer">Wagtail</a>, <a href="https://github.com/zulip/zulip/blob/main/CONTRIBUTING.md#ai-use-policy-and-guidelines" target="_blank" rel="noopener noreferrer">Zulip</a>, and <a href="https://github.com/zulip/zulip/blob/main/CONTRIBUTING.md#ai-use-policy-and-guidelines" target="_blank" rel="noopener noreferrer">others</a>.</p>
<h3 id="slonik-says-i-havent-noticed-1">Slonik says &ldquo;I haven&rsquo;t noticed&hellip;&rdquo;<a class="anchor-link" id="slonik-says-i-havent-noticed"></a></h3>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/06/Jan-waterfall.png" alt="Beware of elephants drowning in AI slop"></figure>
</p>
<p>What is interesting that at this moment PostgreSQL does not have any official policy of this sort available. While this may be a problem that does not touch PostgreSQL as a database server it already has an impact on the PostgreSQL ecosystem consisting of many other extensions and tools. The reason may be quite trivial &ndash; even with AI the entry threshold for PostgreSQL core hacking is still higher than any other tool. Hackers communicate via mailing lists and even with adoption of modern tools like Hackorum.dev it&rsquo;s still not that easy to work with PostgreSQL comparing to a lot of other more tempting tools.</p>
<p>The issue as I often see it for PostgreSQL is that there is not much leadership for the ecosystem from the core. Availability of responsible AI usage policies for the Ecosystem could make the life of maintainers easier and let&rsquo;s be honest, for a lot of smaller projects that&rsquo;s a burden they could be spared. Seems like any help would be appreciated.</p>
<h4 id="what-now-is-this-over-was-this-a-rant-1">What now? Is this over? Was this a rant?</h4>
<p>I like to say, and repeat myself that &ldquo;AI usage in open source is all about the respect&rdquo;. To me this is enough to say all that&rsquo;s needed. People need to communicate. This was meant as a start of the discussion.</p>
<p>PGConf.EU is coming in October, as well as many smaller meetups this year. Lots of space for hallway track discussion and hopefully some outcomes. Not to mention async communication channels. What I hope is we can leverage all these channels to propose some solutions, experiment and get better.</p>
<p>Let it be a call to action to help us all be more reasonable and respectful to others time.</p>
<p>With this in mind check out my original text I refined with AI if you want to see how it changed. Because of course I polished it to some extent to ensure that the grammar and phrasing is cleaner and crispier &#128578;</p>
</details>
<p>Because of course I polished it a little to make sure the grammar and phrasing are cleaner and crispier &#128578;</p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/06/26/why-postgresql-needs-an-ai-usage-policy/">Why PostgreSQL needs an AI usage policy</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Passbolt renews its support for MariaDB Foundation</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/passbolt-renews-its-support-for-mariadb-foundation/" />
      <id>https://mariadb.org/passbolt-renews-its-support-for-mariadb-foundation/</id>
      <updated>2026-06-25T06:23:50+00:00</updated>
      <author><name>Anna Widenius</name></author>
      <summary type="html"><![CDATA[<p>MariaDB Foundation is pleased to announce that Passbolt has renewed its Silver sponsorship for another year, continuing its long-term support for the MariaDB open-source ecosystem. …<br />
Continue reading \"Passbolt renews its support for MariaDB Foundation\"<br />
The post Passbolt renews its support for MariaDB Foundation appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/passbolt-renews-its-support-for-mariadb-foundation/">Passbolt renews its support for MariaDB Foundation</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB Foundation is pleased to announce that <a href="https://www.passbolt.com/">Passbolt</a> has renewed its <a href="https://mariadb.org/donate/#silver-tier-from-eur-5000-per-year">Silver sponsorship</a> for another year, continuing its long-term support for the MariaDB open-source ecosystem. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/passbolt-renews-its-support-for-mariadb-foundation/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Passbolt renews its support for MariaDB Foundation&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/passbolt-renews-its-support-for-mariadb-foundation/">Passbolt renews its support for MariaDB Foundation</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/passbolt-renews-its-support-for-mariadb-foundation/">Passbolt renews its support for MariaDB Foundation</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Aqtra Joins MariaDB Foundation as a Gold Sponsor</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/aqtra-joins-mariadb-foundation-as-a-gold-sponsor/" />
      <id>https://mariadb.org/aqtra-joins-mariadb-foundation-as-a-gold-sponsor/</id>
      <updated>2026-06-24T08:31:00+00:00</updated>
      <author><name>Anna Widenius</name></author>
      <summary type="html"><![CDATA[<p>MariaDB Foundation is pleased to welcome Aqtra Platform as a new Gold Sponsor.<br />
Aqtra is a Development Infrastructure Layer (DIL) platform for building ERP solutions, business applications, internal and external portals, and workflows that connect multiple systems. …<br />
Continue reading \"Aqtra Joins MariaDB Foundation as a Gold Sponsor\"<br />
The post Aqtra Joins MariaDB Foundation as a Gold Sponsor appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/aqtra-joins-mariadb-foundation-as-a-gold-sponsor/">Aqtra Joins MariaDB Foundation as a Gold Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB Foundation is pleased to welcome <a href="http://Aqtra%20Joins%20MariaDB%20Foundation%20as%20a%20Gold%20Sponsor%20MariaDB%20Foundation%20is%20pleased%20to%20welcome%20Aqtra%20as%20a%20new%20Gold%20Sponsor.%20Aqtra%20is%20a%20Development%20Infrastructure%20Layer%20(DIL)%20platform%20for%20building%20ERP%20solutions,%20business%20applications,%20internal%20and%20external%20portals,%20and%20workflows%20that%20connect%20multiple%20systems.%20The%20platform%20provides%20the%20underlying%20architecture,%20governance,%20integration,%20and%20runtime%20capabilities%20required%20to%20develop%20and%20operate%20business%20applications%20at%20scale,%20helping%20organisations%20automate%20complex%20processes%20without%20building%20and%20maintaining%20every%20application%20from%20scratch.%20As%20part%20of%20the%20next%20stage%20of%20its%20platform%20evolution,%20Aqtra%20has%20selected%20MariaDB%20Server%20as%20the%20strategic%20database%20foundation%20for%20its%20next-generation%20architecture.%20By%20joining%20MariaDB%20Foundation%20as%20a%20Gold%20Sponsor,%20Aqtra%20is%20strengthening%20its%20connection%20with%20the%20MariaDB%20ecosystem%20and%20creating%20a%20foundation%20for%20broader%20collaboration%20around%20MariaDB-powered%20business%20automation.%20Building%20Business%20Applications%20on%20MariaDB%20Businesses%20often%20depend%20on%20many%20different%20systems%20for%20finance,%20purchasing,%20inventory,%20sales,%20reporting,%20human%20resources,%20customer%20management,%20and%20internal%20approvals.%20Connecting%20these%20systems%20can%20become%20expensive%20and%20difficult%20to%20maintain.%20Even%20a%20relatively%20simple%20business%20process%20may%20require%20data%20to%20move%20between%20several%20applications,%20with%20custom%20integrations,%20manual%20steps,%20and%20separate%20user%20interfaces.%20Aqtra%20provides%20a%20unified%20platform%20for%20building%20applications%20and%20workflows%20around%20an%20organisation%E2%80%99s%20data,%20processes,%20and%20existing%20systems.%20These%20can%20include%20ERP%20applications,%20procurement%20and%20supplier%20portals,%20CRM%20systems,%20inventory%20management,%20reporting%20solutions,%20HR%20applications,%20help%20desks,%20customer%20portals,%20and%20other%20operational%20tools.%20MariaDB%20Server%20will%20serve%20as%20the%20open%20source%20relational%20database%20foundation%20beneath%20the%20Aqtra%20platform.%20For%20the%20end%20user,%20the%20database%20may%20remain%20largely%20invisible.%20They%20interact%20with%20the%20applications,%20portals,%20reports,%20and%20workflows%20that%20help%20them%20run%20their%20business.%20Underneath%20those%20applications,%20MariaDB%20provides%20the%20data%20layer%20required%20to%20store%20and%20manage%20business%20information,%20application%20configuration,%20workflow%20state,%20and%20operational%20data.%20Aqtra%E2%80%99s%20decision%20therefore%20represents%20an%20important%20form%20of%20MariaDB%20adoption:%20MariaDB%20becomes%20part%20of%20the%20platform%20architecture%20and,%20through%20it,%20part%20of%20the%20applications%20deployed%20for%20Aqtra%20customers.%20Rather%20than%20being%20adopted%20for%20a%20single%20application,%20MariaDB%20becomes%20part%20of%20the%20underlying%20infrastructure%20used%20to%20deliver%20entire%20business%20solution%20stacks.%20As%20Aqtra%20is%20deployed%20across%20customers,%20industries,%20and%20cloud%20environments,%20MariaDB%20becomes%20a%20foundational%20component%20of%20each%20resulting%20application%20ecosystem.%20From%20Cloud%20Infrastructure%20to%20Business%20Automation%20Aqtra%20is%20designed%20to%20run%20within%20infrastructure%20selected%20by%20the%20customer%20or%20its%20service%20provider.%20This%20creates%20an%20opportunity%20for%20cloud%20providers,%20hosting%20companies,%20managed%20service%20providers,%20and%20other%20infrastructure%20partners%20to%20offer%20more%20than%20computing,%20storage,%20and%20hosting.%20Through%20Aqtra,%20infrastructure%20providers%20can%20extend%20their%20role%20beyond%20infrastructure%20delivery%20and%20offer%20a%20complete%20business%20application%20platform%20built%20on%20open%20technologies,%20with%20MariaDB%20serving%20as%20the%20core%20data%20foundation.%20A%20MariaDB-powered%20Aqtra%20deployment%20can%20enable%20providers%20to%20offer:%20ERP%20and%20business%20applications%20hosted%20within%20the%20customer%E2%80%99s%20selected%20infrastructure%20Internal,%20customer,%20supplier,%20and%20partner%20portals%20Automation%20of%20workflows%20spanning%20multiple%20systems%20Greater%20control%20over%20data%20location%20and%20deployment%20architecture%20An%20open%20source%20database%20foundation%20for%20business-critical%20applications%20A%20platform%20that%20can%20expand%20as%20the%20customer%E2%80%99s%20requirements%20grow%20This%20model%20is%20particularly%20relevant%20for%20organisations%20that%20need%20greater%20control%20over%20their%20data%20and%20infrastructure,%20including%20companies%20operating%20in%20regulated%20industries,%20public-sector%20organisations,%20and%20businesses%20looking%20for%20alternatives%20to%20fragmented%20collections%20of%20SaaS%20products.%20Expanding%20the%20MariaDB%20Ecosystem%20The%20collaboration%20between%20Aqtra%20and%20MariaDB%20Foundation%20will%20build%20on%20Aqtra%E2%80%99s%20adoption%20of%20MariaDB%20Server%20as%20a%20core%20component%20of%20its%20Development%20Infrastructure%20Layer%20architecture.%20The%20two%20organisations%20will%20explore%20opportunities%20to%20document%20the%20resulting%20architecture,%20develop%20practical%20deployment%20and%20integration%20materials,%20and%20present%20Aqtra%20as%20part%20of%20the%20broader%20ecosystem%20of%20applications%20and%20platforms%20built%20on%20MariaDB.%20The%20partnership%20will%20also%20focus%20on%20helping%20cloud%20and%20infrastructure%20providers%20understand%20how%20MariaDB%20and%20Aqtra%20can%20work%20together%20as%20a%20complete%20data%20and%20application%20platform.%20Potential%20areas%20of%20collaboration%20include:%20Technical%20and%20architectural%20content%20Deployment%20guides%20and%20reference%20architectures%20MariaDB%20Ecosystem%20Hub%20visibility%20Joint%20webinars,%20presentations,%20and%20case%20studies%20Cloud%20and%20service-provider%20deployment%20models%20Business%20automation%20and%20ERP-focused%20Solution%20Stacks%20MariaDB-powered%20ERP%20and%20business%20automation%20reference%20architectures%20Solution%20stacks%20for%20cloud%20providers%20and%20managed%20service%20providers%20Through%20these%20activities,%20Aqtra%20will%20be%20able%20to%20share%20its%20MariaDB%20experience%20with%20users,%20developers,%20and%20infrastructure%20partners,%20while%20MariaDB%20Foundation%20gains%20an%20important%20new%20application-platform%20use%20case.%20MariaDB%20Adoption%20Through%20Application%20Platforms%20MariaDB%20adoption%20often%20happens%20beneath%20the%20applications%20users%20interact%20with%20every%20day.%20A%20company%20may%20never%20make%20a%20direct%20database%20selection%20for%20every%20business%20application%20it%20uses.%20Instead,%20it%20chooses%20a%20platform,%20service,%20or%20solution%20whose%20architecture%20already%20includes%20MariaDB.%20Application%20platforms%20such%20as%20Aqtra%20can%20therefore%20play%20an%20important%20role%20in%20growing%20the%20MariaDB%20ecosystem.%20By%20embedding%20MariaDB%20into%20a%20reusable%20application%20infrastructure%20layer,%20adoption%20can%20scale%20across%20many%20applications,%20customers,%20and%20deployment%20environments%20without%20requiring%20each%20organisation%20to%20independently%20standardise%20on%20a%20database%20platform.%20As%20the%20platform%20is%20deployed%20for%20new%20customers,%20industries,%20and%20infrastructure%20environments,%20MariaDB%20becomes%20part%20of%20each%20resulting%20application%20architecture.%20%E2%80%9CAqtra%E2%80%99s%20decision%20to%20build%20its%20next%20platform%20database%20layer%20on%20MariaDB%20demonstrates%20how%20MariaDB%20can%20serve%20as%20the%20dependable%20open%20source%20foundation%20beneath%20a%20broad%20range%20of%20business%20applications.%20Aqtra%20brings%20a%20distinctive%20combination%20of%20model-driven%20development,%20ERP%20functionality,%20portals,%20and%20cross-system%20workflow%20automation,%20while%20opening%20new%20opportunities%20with%20cloud%20and%20infrastructure%20providers.%20We%20are%20delighted%20to%20welcome%20Aqtra%20as%20a%20Gold%20Sponsor%20of%20MariaDB%20Foundation.%E2%80%9D%20Anna%20Widenius%20CEO,%20MariaDB%20Foundation%20%E2%80%9CAt%20Aqtra,%20we%20are%20building%20a%20Development%20Infrastructure%20Layer%20that%20enables%20organisations,%20cloud%20providers,%20and%20service%20partners%20to%20create%20and%20operate%20business%20applications,%20ERP%20solutions,%20portals,%20and%20automation%20services%20on%20a%20common%20foundation.%20MariaDB%20stood%20out%20as%20a%20mature,%20reliable,%20and%20truly%20open%20database%20platform%20that%20aligns%20with%20our%20long-term%20architectural%20vision.%20By%20joining%20MariaDB%20Foundation%20as%20a%20Gold%20Sponsor,%20we%20are%20not%20only%20adopting%20MariaDB%20as%20a%20strategic%20technology%20component%20but%20also%20supporting%20the%20ecosystem%20that%20helps%20organisations%20build%20and%20operate%20business-critical%20applications%20on%20open%20infrastructure.%E2%80%9D%20Ilia%20Kors%20CTO%20&amp;%20Co-Founder,%20Aqtra%20Supporting%20the%20Future%20of%20MariaDB%20Server%20Aqtra%E2%80%99s%20Gold%20sponsorship%20directly%20supports%20MariaDB%20Foundation%E2%80%99s%20work%20to%20advance%20MariaDB%20Server,%20facilitate%20technical%20collaboration,%20and%20ensure%20that%20MariaDB%20remains%20open,%20accessible,%20and%20dependable%20for%20organisations%20around%20the%20world.%20It%20also%20gives%20Aqtra%20a%20closer%20connection%20with%20the%20MariaDB%20community%20as%20the%20company%20develops%20its%20MariaDB-based%20architecture%20and%20expands%20the%20platform%20across%20new%20infrastructure%20environments.%20We%20warmly%20welcome%20Aqtra%20to%20the%20MariaDB%20Foundation%20sponsor%20community%20and%20look%20forward%20to%20working%20together%20to%20expand%20the%20role%20of%20MariaDB%20in%20ERP,%20business%20automation,%20and%20cross-system%20application%20development.%20Learn%20more%20about%20Aqtra:%20https://aqtra.io/%20Learn%20more%20about%20MariaDB%20Foundation%20sponsorship:%20https://mariadb.org/donate/">Aqtra</a> Platform as a new Gold Sponsor.<br>
Aqtra is a Development Infrastructure Layer (DIL) platform for building ERP solutions, business applications, internal and external portals, and workflows that connect multiple systems. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/aqtra-joins-mariadb-foundation-as-a-gold-sponsor/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Aqtra Joins MariaDB Foundation as a Gold Sponsor&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/aqtra-joins-mariadb-foundation-as-a-gold-sponsor/">Aqtra Joins MariaDB Foundation as a Gold Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/aqtra-joins-mariadb-foundation-as-a-gold-sponsor/">Aqtra Joins MariaDB Foundation as a Gold Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB 13.1 Preview: This One Is Full of Community Goodies!</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-13-1-preview-this-one-is-full-of-community-goodies/" />
      <id>https://mariadb.org/mariadb-13-1-preview-this-one-is-full-of-community-goodies/</id>
      <updated>2026-06-23T05:12:41+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>We just announced the availability of a preview of the MariaDB 13.1 series.<br />
MariaDB 13.1 is a rolling release preview, and, as usual, this is the right moment to test what is coming, give feedback, and help us polish the next MariaDB Server release. …<br />
Continue reading \"MariaDB 13.1 Preview: This One Is Full of Community Goodies!\"<br />
The post MariaDB 13.1 Preview: This One Is Full of Community Goodies! appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-13-1-preview-this-one-is-full-of-community-goodies/">MariaDB 13.1 Preview: This One Is Full of Community Goodies!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>We <a href="https://mariadb.org/mariadb-13-1-preview-available/">just announced the availability of a preview of the MariaDB 13.1</a> series.<br>
MariaDB 13.1 is a rolling release preview, and, as usual, this is the right moment to test what is coming, give feedback, and help us polish the next MariaDB Server release. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-13-1-preview-this-one-is-full-of-community-goodies/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB 13.1 Preview: This One Is Full of Community Goodies!&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-13-1-preview-this-one-is-full-of-community-goodies/">MariaDB 13.1 Preview: This One Is Full of Community Goodies!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-13-1-preview-this-one-is-full-of-community-goodies/">MariaDB 13.1 Preview: This One Is Full of Community Goodies!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB 13.1 preview available</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-13-1-preview-available/" />
      <id>https://mariadb.org/mariadb-13-1-preview-available/</id>
      <updated>2026-06-20T20:24:51+00:00</updated>
      <author><name>Sergei</name></author>
      <summary type="html"><![CDATA[<p>We are pleased to announce the availability of a preview of the MariaDB 13.1 series. MariaDB 13.1 will be a rolling release. …<br />
Continue reading \"MariaDB 13.1 preview available\"<br />
The post MariaDB 13.1 preview available appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-13-1-preview-available/">MariaDB 13.1 preview available</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>We are pleased to announce the availability of a preview of the <a href="https://mariadb.com/docs/release-notes/community-server/13.1/mariadb-13.1-changes-and-improvements" target="_blank" rel="noreferrer noopener">MariaDB 13.1</a> series. MariaDB 13.1 will be a <a href="https://mariadb.com/kb/en/mariadb-release-model/" target="_blank" rel="noreferrer noopener">rolling release</a>. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-13-1-preview-available/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB 13.1 preview available&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-13-1-preview-available/">MariaDB 13.1 preview available</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-13-1-preview-available/">MariaDB 13.1 preview available</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Simple tool to build MariaDB commits for performance-change analysis</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/simple-tool-to-build-mariadb-commits-for-performance-change-analysis/" />
      <id>https://mariadb.org/simple-tool-to-build-mariadb-commits-for-performance-change-analysis/</id>
      <updated>2026-06-18T21:10:43+00:00</updated>
      <author><name>Jonathan Miller</name></author>
      <summary type="html"><![CDATA[<p>Tracking down changes in database performance is one of the hardest parts of engineering, especially when the change is buried somewhere in a long commit history. …<br />
Continue reading \"Simple tool to build MariaDB commits for performance-change analysis\"<br />
The post Simple tool to build MariaDB commits for performance-change analysis appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/simple-tool-to-build-mariadb-commits-for-performance-change-analysis/">Simple tool to build MariaDB commits for performance-change analysis</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Tracking down changes in database performance is one of the hardest parts of engineering, especially when the change is buried somewhere in a long commit history. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/simple-tool-to-build-mariadb-commits-for-performance-change-analysis/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Simple tool to build MariaDB commits for performance-change analysis&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/simple-tool-to-build-mariadb-commits-for-performance-change-analysis/">Simple tool to build MariaDB commits for performance-change analysis</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/simple-tool-to-build-mariadb-commits-for-performance-change-analysis/">Simple tool to build MariaDB commits for performance-change analysis</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Vector in Laravel: insights on choosing an embedding model</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-vector-in-laravel-insights-on-choosing-an-embedding-model/" />
      <id>https://mariadb.org/mariadb-vector-in-laravel-insights-on-choosing-an-embedding-model/</id>
      <updated>2026-06-18T06:18:15+00:00</updated>
      <author><name>Robert Silén</name></author>
      <summary type="html"><![CDATA[<p>laravel-mariadb-vector is an open-source project by Erik Ros, bringing MariaDB’s native vector search to Laravel’s Eloquent ORM. In his guest post, Erik shares how it works, and his insights about picking an embedding model. …<br />
Continue reading \"MariaDB Vector in Laravel: insights on choosing an embedding model\"<br />
The post MariaDB Vector in Laravel: insights on choosing an embedding model appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-vector-in-laravel-insights-on-choosing-an-embedding-model/">MariaDB Vector in Laravel: insights on choosing an embedding model</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><a href="https://packagist.org/packages/devilsberg/laravel-mariadb-vector">laravel-mariadb-vector</a>&nbsp;is an open-source project by Erik Ros, bringing MariaDB&rsquo;s native vector search to Laravel&rsquo;s Eloquent ORM. In his guest post, Erik shares how it works, and his insights about picking an embedding model. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-vector-in-laravel-insights-on-choosing-an-embedding-model/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Vector in Laravel: insights on choosing an embedding model&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-vector-in-laravel-insights-on-choosing-an-embedding-model/">MariaDB Vector in Laravel: insights on choosing an embedding model</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-vector-in-laravel-insights-on-choosing-an-embedding-model/">MariaDB Vector in Laravel: insights on choosing an embedding model</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Security advisory: CVE-2026-9740 and CVE-2026-11933 in Percona Server for MongoDB</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/security-advisory-cve-2026-9740-and-cve-2026-11933-in-percona-server-for-mongodb/" />
      <id>https://www.percona.com/blog/security-advisory-cve-2026-9740-and-cve-2026-11933-in-percona-server-for-mongodb/</id>
      <updated>2026-06-17T14:24:02+00:00</updated>
      <author><name>Radoslaw Szulgo</name></author>
      <summary type="html"><![CDATA[<p>TL;DR: This advisory covers the two most important high-severity memory-safety vulnerabilities affecting MongoDB Community and our downstream Percona Server for MongoDB – CVE-2026-11933 and CVE-2026-9740. Both will be addressed in a single coordinated patch release, bundled with other recently revealed lower-scored CVE fixes: CVE-2026-9753, CVE-2026-9752, CVE-2026-9751, CVE-2026-9750, CVE-2026-9749, CVE-2026-9748, CVE-2026-9747, CVE-2026-9746, CVE-2026-9743, and CVE-2026-9741. Fixes land … Continued<br />
The post Security advisory: CVE-2026-9740 and CVE-2026-11933 in Percona Server for MongoDB appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/security-advisory-cve-2026-9740-and-cve-2026-11933-in-percona-server-for-mongodb/">Security advisory: CVE-2026-9740 and CVE-2026-11933 in Percona Server for MongoDB</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p data-sourcepos="7:1-7:352;461-812"><span style="font-weight: 400"><strong>TL;DR:</strong>&nbsp;This advisory covers the two most important high-severity memory-safety vulnerabilities affecting MongoDB Community and our downstream Percona Server for MongoDB &ndash; </span><a href="https://www.cve.org/CVERecord?id=CVE-2026-11933"><i><span style="font-weight: 400">CVE-2026-11933</span></i></a><span style="font-weight: 400"> and </span><a href="https://www.cve.org/CVERecord?id=CVE-2026-9740"><i><span style="font-weight: 400">CVE-2026-9740</span></i></a><span style="font-weight: 400">. Both will be addressed in a single coordinated patch release, bundled with other recently revealed lower-scored CVE fixes: </span><a href="https://www.cve.org/CVERecord?id=CVE-2026-9753"><span style="font-weight: 400">CVE-2026-9753</span></a><span style="font-weight: 400">, </span><a href="https://www.cve.org/CVERecord?id=CVE-2026-9752"><span style="font-weight: 400">CVE-2026-9752</span></a><span style="font-weight: 400">, </span><a href="https://www.cve.org/CVERecord?id=CVE-2026-9751"><span style="font-weight: 400">CVE-2026-9751</span></a><span style="font-weight: 400">, </span><a href="https://www.cve.org/CVERecord?id=CVE-2026-9750"><span style="font-weight: 400">CVE-2026-9750</span></a><span style="font-weight: 400">, </span><a href="https://www.cve.org/CVERecord?id=CVE-2026-9749"><span style="font-weight: 400">CVE-2026-9749</span></a><span style="font-weight: 400">, </span><a href="https://www.cve.org/CVERecord?id=CVE-2026-9748"><span style="font-weight: 400">CVE-2026-9748</span></a><span style="font-weight: 400">, </span><a href="https://www.cve.org/CVERecord?id=CVE-2026-9747"><span style="font-weight: 400">CVE-2026-9747</span></a><span style="font-weight: 400">, </span><a href="http://cve-2026-9746"><span style="font-weight: 400">CVE-2026-9746</span></a><span style="font-weight: 400">, </span><a href="http://cve-2026-9743"><span style="font-weight: 400">CVE-2026-9743</span></a><span style="font-weight: 400">, and </span><a href="https://www.cve.org/CVERecord?id=CVE-2026-9741"><span style="font-weight: 400">CVE-2026-9741</span></a><span style="font-weight: 400">.</span></p>
<p>Fixes land in Percona Server for MongoDB patch window starting next week. The first high-vulnerability issue has nothing between it and your <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">mongod</code> process except your firewall. The second has a configuration off-switch you can flip during a maintenance window. &nbsp;Read on to understand why, how, and what.</p>
<h2 data-sourcepos="11:1-11:58;915-972"><img decoding="async" loading="lazy" class="aligncenter wp-image-49971 size-large" src="https://www.percona.com/wp-content/uploads/2026/06/blog-hero-June-2026-1024x576.png" alt="" width="1024" height="576"><a class="anchor-link" id=""></a></h2>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" data-sourcepos="11:1-11:58;915-972">CVE-2026-9740 &mdash; the one that does not need credentials<a class="anchor-link" id="cve-2026-9740-the-one-that-does-not-need-credentials"></a></h2>
<p>A stack overflow in the BSON validator, specifically in the BSONColumn interleaved-reference handling. The validator&rsquo;s depth tracking resets on mutual recursion between validation functions, so a sufficiently nested input exhausts the thread&rsquo;s stack before any explicit limit fires. The result: <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">mongod</code> crashes.</p>
<p>CVSS 8.7. High severity. The reason it lands in High instead of merely Medium is the prerequisite for exploitation &ndash; there is none.</p>
<p>The attacker needs network reachability to a <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">mongod</code> listener. No credentials, no prior session, and no application interaction. One crafted message over the wire and the process is down. Repeated crashes are trivially repeatable, so an attacker who can reach the port can keep the instance offline for as long as they keep that reachability. The urgency of this issue comes from the audience &ndash; everyone with a TCP route to your database.</p>
<p>Upstream tracking: <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://jira.mongodb.org/browse/SERVER-125063">SERVER-125063</a>. Affected versions are Percona Server for MongoDB 8.0 &le; 8.0.23-10 and PSMDB 7.0 &le; 7.0.34-19. The vulnerable BSONColumn code path was introduced in 7.0, so 6.0 and earlier are not in scope for this one.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" data-sourcepos="25:1-25:55;2365-2419">CVE-2026-11933 &mdash; the one that does need credentials and permissions to read<a class="anchor-link" id="cve-2026-11933-the-one-that-does-need-credentials-and-permissions-to-read"></a></h2>
<p><span style="font-weight: 400">The vulnerable code path is inside MongoDB Server&rsquo;s server-side JavaScript engine, specifically in the BSON-to-array conversion routine. When a BSON document is materialized as a JavaScript array for use inside a server-side script, the engine can reach a state where it accesses memory that has already been freed. An attacker who can submit input that flows into that conversion path can shape what happens at the point of access.</span></p>
<p><b>Server-side JavaScript is reachable from the following surfaces:</b></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">The </span><span style="font-weight: 400">$where</span><span style="font-weight: 400"> query operator (deprecated in 8.0).</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The </span><span style="font-weight: 400">$function</span><span style="font-weight: 400"> aggregation expression (deprecated in 8.0).</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The </span><span style="font-weight: 400">$accumulator</span><span style="font-weight: 400"> aggregation expression (deprecated in 8.0).</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The </span><span style="font-weight: 400">mapReduce</span><span style="font-weight: 400"> command (deprecated since 5.0).</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">JavaScript functions stored in </span><a href="http://system.js"><span style="font-weight: 400">system.js</span></a><span style="font-weight: 400">.</span></li>
</ol>
<p><span style="font-weight: 400">MongoDB logs a warning when you run deprecated functions.</span></p>
<p><b><br>
</b><b>Prerequisites for exploitation:</b></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">The attacker must be authenticated to MongoDB.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The attacker must hold any role that permits running queries or aggregations against a collection. The built-in </span><span style="font-weight: 400">read</span><span style="font-weight: 400"> role on a single database is sufficient.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Server-side JavaScript must be enabled on the </span><span style="font-weight: 400">mongod</span><span style="font-weight: 400"> instance. This is the default; many production deployments leave it enabled even when they do not use it.</span></li>
</ol>
<p>CVSS 8.8. High severity. Two demonstrated outcomes:</p>
<ul>
<li>Information disclosure (reading other content out of the <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">mongod</code> process memory) and</li>
<li>Denial of Service (crashing it).</li>
</ul>
<p>Upstream tracking: <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://jira.mongodb.org/browse/SERVER-128125">SERVER-128125</a>. Affected versions: every supported and End of Life Percona Server for MongoDB major from 4.4 through 8.0.</p>
<p data-sourcepos="31:1-31:160;3100-3259"><img decoding="async" loading="lazy" class="aligncenter wp-image-49973 size-full" src="https://www.percona.com/wp-content/uploads/2026/06/blog-11933-attack-flow.png" alt="" width="1600" height="860"></p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" data-sourcepos="43:1-43:37;4603-4639">The good news and bad news<a class="anchor-link" id="the-good-news-and-bad-news"></a></h2>
<p>CVE-2026-11933 has a configuration off-switch. If your application does not use server-side JavaScript &mdash; <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">$where</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">$function</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">$accumulator</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">mapReduce</code>, or stored <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">system.js</code> functions &mdash; you can disable server-side JavaScript on the server, removing the attack surface entirely until you patch.</p>
<h3><b>How to check whether your applications use server-side JavaScript before disabling:</b><a class="anchor-link" id="how-to-check-whether-your-applications-use-server-side-javascript-before-disabling"></a></h3>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Enable MongoDB profiling at level 2 (all operations) on a representative mongod server for a representative time window. See details in </span><a href="https://www.mongodb.com/docs/manual/tutorial/manage-the-database-profiler/"><span style="font-weight: 400">Manage the database profiler</span></a><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Search the </span><span style="font-weight: 400">system.profile</span><span style="font-weight: 400"> collection for operations that include </span><span style="font-weight: 400">$where</span><span style="font-weight: 400">, </span><span style="font-weight: 400">$function</span><span style="font-weight: 400">, </span><span style="font-weight: 400">$accumulator</span><span style="font-weight: 400">, or </span><span style="font-weight: 400">mapReduce</span><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Inspect application code paths and stored aggregation pipelines for the same operators. Check </span><span style="font-weight: 400">system.js</span><span style="font-weight: 400"> in each database for stored functions.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">If any usage exists, treat disabling as not viable for those deployments and rely on patching plus the defense-in-depth controls below.</span></li>
</ol>
<h3><b>How to disable server-side JavaScript:</b><a class="anchor-link" id="how-to-disable-server-side-javascript"></a></h3>
<p>Add to your configuration file for <code>mongod</code>and <code>mongos</code>:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">security: 
  javascriptEnabled: false</pre>
<p>Or pass <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">--noscripting</code> on the command line. <span style="font-weight: 400">See the reference documentation for details about </span><a href="https://www.mongodb.com/docs/manual/reference/configuration-options/#mongodb-setting-security.javascriptEnabled"><span style="font-weight: 400">MongoDB Setting:&nbsp; security.javascriptEnabled</span></a><span style="font-weight: 400">.</span></p>
<p>After a restart, any operation that reaches for server-side JavaScript will return an error. That is the catch: if your application <em>does</em> use one of those operators, this is not a viable mitigation for you, and you have to wait for the patch. If you are not sure whether your application uses them, turn on the database profiler at level 2 on a representative replica for a window long enough to be representative, then grep the profile collection for the operator names. Several teams have done this exercise in the last forty-eight hours and learned the answer is <em>&ldquo;no, we don&rsquo;t actually use any of that.&rdquo;</em> The cost of disabling is then the cost of a <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">mongod</code> or <code>mongos</code> restart.</p>
<p data-sourcepos="58:1-58:168;5736-5903">That was good news. Now the bad news:&nbsp;CVE-2026-9740 has no equivalent off-switch. The BSON validator is core to every client message; it cannot be disabled. Patch and network controls are the only options.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" data-sourcepos="60:1-60:30;5905-5934">What is shipping, and when<a class="anchor-link" id="what-is-shipping-and-when"></a></h2>
<p>The fixes for both CVEs will land in a single coordinated patch release for each supported major:</p>
<ul class="[li_&amp;]:mb-0 [li_&amp;]:mt-1 [li_&amp;]:gap-1 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3" data-sourcepos="64:1-66:206;6035-6428">
<li><strong>Percona Server for MongoDB 7.0 series</strong> &mdash; fix targeted for&nbsp; <strong>June 23, 2026</strong>.</li>
<li><strong>Percona Server for MongoDB 8.0 series</strong> &mdash; fix targeted for&nbsp; <strong>June 25, 2026</strong>.</li>
<li><strong>Percona Server for MongoDB 6.0 series</strong> &mdash; fix targeted for&nbsp; <strong>June 25, 2026 </strong>(for CVE-2026-11933).</li>
</ul>
<p>All dates are targets, not commitments. Plan one upgrade window covering all CVEs.</p>
<p>Percona is&nbsp;<strong>not</strong> building binary packages for the 5.x line. We&rsquo;re being upfront about that &mdash; the calculus on extended support has a limit, and 5.x is past it for us. If you have a hard requirement on 5.x and the time pressure to meet it, the source is available for building. Percona customers on 5.x can open a ticket, and we&rsquo;ll work on the case individually.</p>
<p>As usual, you can download patches from your package manager or Percona&nbsp;<a href="https://www.percona.com/downloads/">Software Downloads</a>&nbsp;page.</p>
<p>On <strong>Kubernetes via the Percona Operator for MongoDB</strong>: same drill as usual. When the patched image is published, edit the image tag in your&nbsp;<code>PerconaServerMongoDB</code>&nbsp;custom resource and let the operator roll the cluster. Don&rsquo;t wait for the June operator release to do it for you. See details in our documentation on how to&nbsp;<a href="https://docs.percona.com/percona-operator-for-mongodb/update-db.html">Upgrade Percona Server for MongoDB</a>.&nbsp;You do not need to wait for an operator release to apply a security fix.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold" data-sourcepos="72:1-72:24;7030-7053">What to do this week<a class="anchor-link" id="what-to-do-this-week"></a></h2>
<p>In order of urgency, for most deployments:</p>
<ol class="[li_&amp;]:mb-0 [li_&amp;]:mt-1 [li_&amp;]:gap-1 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-decimal flex flex-col gap-1 pl-8 mb-3" data-sourcepos="76:1-79:226;7099-7810">
<li><strong>Confirm your <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">mongod</code> or <code>mongos</code> listeners are not reachable from any source you would not trust with a shell on the host.</strong> If you find an exposure, fix that first. CVE-2026-9740 turns any such exposure into a DoS primitive.</li>
<li><strong>For deployments that do not use server-side JavaScript, disable it.</strong> Full mitigation for CVE-2026-11933 within a single <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">mongod</code> restart.</li>
<li><strong>Plan your upgrade window</strong> for the week the relevant fixed release lands. One window. Both CVEs. Plus, the others scored lower.</li>
<li><strong>Audit which roles in your deployment can run ad-hoc queries or aggregations.</strong> The bar for CVE-2026-11933 is the standard read role, so the population of potential attackers is larger than for most memory-safety defects.</li>
</ol>
<p>One closing point, because it has come up several times in customer conversations this week. For a deployment behind tight network controls, the post-authenticated bug is the more urgent one. For a deployment reachable from broader networks &mdash; public cloud, shared internal LANs, multi-tenant infrastructure &mdash; the pre-authenticated bug is. Triage by <em>your</em> exposure, not by <em>their</em> CVSS.</p>
<hr class="border-border-200 border-t-0.5 my-3 mx-1.5">
<p><em>Questions, or a deployment you&rsquo;re not sure how to triage? Find us on the <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://forums.percona.com/">Percona Forum</a>, or, for customers, in the support portal.</em></p>
<p><i><span style="font-weight: 400">Reviewed by Ivan Groenewold.</span></i><i><span style="font-weight: 400">&nbsp;Vetted for technical accuracy as of June 17, 2026.</span></i></p>
<p>The post <a href="https://www.percona.com/blog/security-advisory-cve-2026-9740-and-cve-2026-11933-in-percona-server-for-mongodb/">Security advisory: CVE-2026-9740 and CVE-2026-11933 in Percona Server for MongoDB</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/security-advisory-cve-2026-9740-and-cve-2026-11933-in-percona-server-for-mongodb/">Security advisory: CVE-2026-9740 and CVE-2026-11933 in Percona Server for MongoDB</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB R2DBC Connector 1.4.1 now available</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/mariadb-r2dbc-connector-1-4-1-now-available/" />
      <id>https://mariadb.com/resources/blog/mariadb-r2dbc-connector-1-4-1-now-available/</id>
      <updated>2026-06-16T22:24:39+00:00</updated>
      <author><name>Daniel Bartholomew</name></author>
      <summary type="html"><![CDATA[<p>MariaDB is pleased to announce the immediate availability of the MariaDB Connector/R2DBC 1.4.1 GA release. Download Now Release Notes MariaDB […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-r2dbc-connector-1-4-1-now-available/">MariaDB R2DBC Connector 1.4.1 now available</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB is pleased to announce the immediate availability of the MariaDB Connector/R2DBC 1.4.1 GA release. Download Now MariaDB Connector/R2DBC 1.4.1 is a Stable (GA) release. Notable items in this release include: See the Connector/R2DBC 1.4.1 release notes page for details and visit mariadb.com/downloads/connectors/connectors-data-access/r2dbc-connector/</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-r2dbc-connector-1-4-1-now-available/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-r2dbc-connector-1-4-1-now-available/">MariaDB R2DBC Connector 1.4.1 now available</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>High Performance Real-Time Analytics on MariaDB Cloud: MariaDB Exa Technical Preview</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/high-performance-real-time-analytics-on-mariadb-cloud-mariadb-exa-technical-preview/" />
      <id>https://mariadb.com/resources/blog/high-performance-real-time-analytics-on-mariadb-cloud-mariadb-exa-technical-preview/</id>
      <updated>2026-06-16T14:59:25+00:00</updated>
      <author><name>Allen Herrera</name></author>
      <summary type="html"><![CDATA[<p>We are excited to announce the technical preview of MariaDB Exa on MariaDB Cloud. This release brings high-performance Hybrid Transactional […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/high-performance-real-time-analytics-on-mariadb-cloud-mariadb-exa-technical-preview/">High Performance Real-Time Analytics on MariaDB Cloud: MariaDB Exa Technical Preview</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>We are excited to announce the technical preview of MariaDB Exa on MariaDB Cloud. This release brings high-performance Hybrid Transactional and Analytical Processing (HTAP) directly into the MariaDB environment by integrating Exasol&rsquo;s massively parallel processing (MPP) engine. By removing the requirement for complex ETL pipelines, MariaDB Exa enables analytics on live transactional data at up to&hellip;</p>
<p><a href="https://mariadb.com/resources/blog/high-performance-real-time-analytics-on-mariadb-cloud-mariadb-exa-technical-preview/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/high-performance-real-time-analytics-on-mariadb-cloud-mariadb-exa-technical-preview/">High Performance Real-Time Analytics on MariaDB Cloud: MariaDB Exa Technical Preview</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Server 10.6 Reaches End of Life on July 6th</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-server-10-6-reaches-end-of-life-on-july-6th/" />
      <id>https://mariadb.org/mariadb-server-10-6-reaches-end-of-life-on-july-6th/</id>
      <updated>2026-06-16T12:26:27+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>MariaDB Server 10.6 has been with us for a long time. It was the first MariaDB LTS release under the current release model, and it has served many users, distributions, applications, and production environments very well. …<br />
Continue reading \"MariaDB Server 10.6 Reaches End of Life on July 6th\"<br />
The post MariaDB Server 10.6 Reaches End of Life on July 6th appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-server-10-6-reaches-end-of-life-on-july-6th/">MariaDB Server 10.6 Reaches End of Life on July 6th</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB Server 10.6 has been with us for a long time. It was the first MariaDB LTS release under the current release model, and it has served many users, distributions, applications, and production environments very well. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-server-10-6-reaches-end-of-life-on-july-6th/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Server 10.6 Reaches End of Life on July 6th&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-server-10-6-reaches-end-of-life-on-july-6th/">MariaDB Server 10.6 Reaches End of Life on July 6th</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-server-10-6-reaches-end-of-life-on-july-6th/">MariaDB Server 10.6 Reaches End of Life on July 6th</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Extending pt-archiver with a Partition-Aware Plug-in for Fast Retention Policy Enforcement</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/extending-pt-archiver-with-a-partition-aware-plug-in-for-fast-retention-policy-enforcement/" />
      <id>https://www.percona.com/blog/extending-pt-archiver-with-a-partition-aware-plug-in-for-fast-retention-policy-enforcement/</id>
      <updated>2026-06-16T11:31:51+00:00</updated>
      <author><name>Corrado Pandiani</name></author>
      <summary type="html"><![CDATA[<p>Managing data retention policies is one of the most common operational tasks in MySQL. Applications continuously generate transactional, audit, logging, telemetry, and event data. Over time, these tables can grow to billions of rows, causing: Larger backups Longer recovery times Reduced buffer pool efficiency Slower index maintenance Increased storage costs Degraded query performance To address … Continued<br />
The post Extending pt-archiver with a Partition-Aware Plug-in for Fast Retention Policy Enforcement appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/extending-pt-archiver-with-a-partition-aware-plug-in-for-fast-retention-policy-enforcement/">Extending pt-archiver with a Partition-Aware Plug-in for Fast Retention Policy Enforcement</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Managing data retention policies is one of the most common operational tasks in MySQL.</p>
<p>Applications continuously generate transactional, audit, logging, telemetry, and event data. Over time, these tables can grow to billions of rows, causing:</p>
<ul>
<li>Larger backups</li>
<li>Longer recovery times</li>
<li>Reduced buffer pool efficiency</li>
<li>Slower index maintenance</li>
<li>Increased storage costs</li>
<li>Degraded query performance</li>
</ul>
<p>To address these problems, organizations typically implement retention policies based on dates or timestamps. Examples include deleting events older than 90 days or purging session data older than 30 days and so forth. The deleted data can then eventually be archived somewhere else, like in another DBMS or on external files.</p>
<p>One of the most widely used tools for implementing these policies in MySQL ecosystems is pt-archiver, part of the Percona Toolkit.</p>
<p>This article provides a review of what pt-archiver is and how to use it, but in particular it focuses on the fact this tool is not partitioning aware, and this can make the deletion phase more costly. The article shows how to extend pt-archiver with a Perl plugin to make it aware of partitioning.</p>
<p>&nbsp;</p>
<h2>What is pt-archiver?<a class="anchor-link" id="what-is-pt-archiver"></a></h2>
<p>pt-archiver is a command-line utility from Percona Toolkit designed to:</p>
<ul>
<li>Archive rows from MySQL tables</li>
<li>Purge rows from MySQL tables</li>
<li>Move data between tables into the local database or a remote one</li>
<li>Export rows into files</li>
</ul>
<p>In a few words: implementing retention policies safely.</p>
<p>The tool processes rows incrementally in chunks, avoiding massive transactions and reducing impact on production systems.</p>
<p>Example:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">pt-archiver 
&nbsp;&nbsp;--source h=localhost,D=mydb,t=events 
&nbsp;&nbsp;--where "created_at &amp;lt; '2026-05-01'" 
&nbsp;&nbsp;--purge 
&nbsp;&nbsp;--limit 1000 
&nbsp;&nbsp;--commit-each</pre>
<p>This command:</p>
<ul>
<li>Scans rows matching the WHERE condition</li>
<li>Processes them in chunks of 1000 rows</li>
<li>Commits every chunk</li>
<li>Deletes matching rows from the source table</li>
</ul>
<p>pt-archiver provides several advantages compared to ad-hoc DELETE statements.</p>
<p>Instead of running:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">DELETE FROM events
WHERE created_at &amp;lt; '2026-05-01';</pre>
<p>which may:</p>
<ul>
<li>Lock rows for a long time</li>
<li>Generate massive undo/redo logs</li>
<li>Create replication lag</li>
<li>Exhaust transaction logs</li>
</ul>
<p>pt-archiver processes rows incrementally to make the process overhead less impactful for the database performance.</p>
<p>pt-archiver implementation permits flexible archival strategies</p>
<p>Rows can be copied to another table on a remote host, exported to files or removed completely</p>
<p>More details: <a href="https://docs.percona.com/percona-toolkit/pt-archiver.html#extending">ps://docs.percona.com/percona-toolkit/pt-archiver.html</a></p>
<h2><a class="anchor-link" id=""></a></h2>
<h3>Example: Copy rows to a remote archive table<a class="anchor-link" id="example-copy-rows-to-a-remote-archive-table"></a></h3>
<p>The following example archives rows older than 90 days from a local table into an archive table hosted on a remote MySQL server:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">pt-archiver 
&nbsp;&nbsp;--source h=localhost,D=sales,t=orders,u=archiver,p=secret 
&nbsp;&nbsp;--dest h=archive-server,D=archive,t=orders_archive,u=archiver,p=secret 
&nbsp;&nbsp;--where "created_at &amp;lt; '2026-05-01'" 
&nbsp;&nbsp;--limit 1000 
&nbsp;&nbsp;--commit-each 
&nbsp;&nbsp;--progress 10000 
&nbsp;&nbsp;--statistics</pre>
<p>In this example:</p>
<ul>
<li><span style="color: #339966">&ndash;source</span> defines the source table</li>
<li><span style="color: #339966">&ndash;dest</span> defines the remote archive destination</li>
<li><span style="color: #339966">&ndash;where</span> selects rows eligible for archival</li>
<li><span style="color: #339966">&ndash;limit</span> controls batch size</li>
<li><span style="color: #339966">&ndash;commit-each</span> commits every batch independently to reduce transaction overhead</li>
</ul>
<p>&ndash;<span style="color: #339966">-progress</span> reports progress every 10,000 rows</p>
<p>If rows should be removed from the source table after being copied, add <span style="color: #339966">&ndash;purge</span></p>
<h3>Example: Export rows to a file<a class="anchor-link" id="example-export-rows-to-a-file"></a></h3>
<p>The following example exports rows older than one year into a text file:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">pt-archiver 
&nbsp;&nbsp;--source h=localhost,D=sales,t=orders,u=archiver,p=secret 
&nbsp;&nbsp;--where "created_at &amp;lt; NOW() - INTERVAL 1 YEAR" 
&nbsp;&nbsp;--file '/tmp/orders_archive_%Y-%m-%d.txt' 
&nbsp;&nbsp;--output-format csv 
&nbsp;&nbsp;--limit 1000 
&nbsp;&nbsp;--commit-each 
&nbsp;&nbsp;--progress 10000 
&nbsp;&nbsp;--statistics</pre>
<p>In this example:</p>
<ul>
<li><span style="color: #339966">&ndash;file</span> specifies the output file</li>
<li>&ndash;<span style="color: #339966">-output-format csv</span> exports rows in CSV format</li>
<li>Date placeholders in the filename are expanded automatically</li>
</ul>
<p>Rows can optionally be deleted from the source table by adding <span style="color: #339966">&ndash;purge</span></p>
<p>This allows pt-archiver to be used both for data retention and for offline archival workflows.</p>
<h1><a class="anchor-link" id=""></a></h1>
<h2>The Hidden Cost of DELETE Statements<a class="anchor-link" id="the-hidden-cost-of-delete-statements"></a></h2>
<p>Although pt-archiver is much safer than massive DELETE operations, it still fundamentally relies on DELETE statements.</p>
<p>This is a critical point.</p>
<p>Even when there are proper indexes, the rows are processed in chunks, and transactions are small; the large-scale DELETE operations remain expensive.</p>
<p>Deleting rows is expensive in InnoDB because it involves:</p>
<ul>
<li>Locating rows via indexes</li>
<li>Modifying clustered indexes</li>
<li>Modifying secondary indexes</li>
<li>Generating undo logs</li>
<li>Generating redo logs</li>
<li>Purge thread processing</li>
<li>Replication event generation</li>
<li>Page fragmentation</li>
</ul>
<p>When deleting billions of rows, the overhead becomes enormous.</p>
<p>Indexes help for sure, but only partially.</p>
<p>Consider:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">DELETE FROM events
WHERE created_at &amp;lt; '2024-01-01';</pre>
<p>If <span style="color: #339966">created_at</span> is indexed, MySQL can efficiently locate rows.</p>
<p>However, locating rows efficiently is only part of the cost. The actual delete operations still require all those things we mentioned above.</p>
<p>At considerable scale, this becomes expensive.</p>
<h1><a class="anchor-link" id=""></a></h1>
<h2>Why RANGE Partitioning is Superior for Retention Policies<a class="anchor-link" id="why-range-partitioning-is-superior-for-retention-policies"></a></h2>
<p>For time-based retention policies, partitioning is often dramatically more efficient. In particular, RANGE partitioning is very useful for these cases.</p>
<p>Example:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE events (
&nbsp;&nbsp;&nbsp;&nbsp;id BIGINT NOT NULL,
&nbsp;&nbsp;&nbsp;&nbsp;created_at DATETIME NOT NULL,
&nbsp;&nbsp;&nbsp;&nbsp;payload JSON,
&nbsp;&nbsp;&nbsp;&nbsp;PRIMARY KEY(id, created_at)
)

PARTITION BY RANGE (TO_DAYS(created_at)) (
&nbsp;&nbsp;&nbsp;&nbsp;PARTITION p202604 VALUES LESS THAN (TO_DAYS('2026-05-01')),
&nbsp;&nbsp;&nbsp;&nbsp;PARTITION p202605 VALUES LESS THAN (TO_DAYS('2026-06-01')),
&nbsp;&nbsp;&nbsp;&nbsp;PARTITION p202606 VALUES LESS THAN (TO_DAYS('2026-07-01'))
);</pre>
<p>With partitioning, dropping old data becomes:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">ALTER TABLE events DROP PARTITION p202604;</pre>
<p>This operation is dramatically faster than running a DELETE.</p>
<p>Dropping a partition:</p>
<ul>
<li>Removes an entire physical partition</li>
<li>Avoids row-by-row DELETE</li>
<li>Avoids undo generation for each row</li>
<li>Avoids secondary index maintenance per row</li>
<li>Minimizes redo generation</li>
<li>Is nearly metadata-only</li>
</ul>
<p>This can remove millions or billions of rows in a matter of seconds without the same large cost of DELETE.</p>
<h1><a class="anchor-link" id=""></a></h1>
<h2>The Problem: pt-archiver is Not Partition-Aware<a class="anchor-link" id="the-problem-pt-archiver-is-not-partition-aware"></a></h2>
<p>Unfortunately, pt-archiver does not automatically understand partitioning strategies.</p>
<p>Even if the table is partitioned or the retention policy perfectly matches partition boundaries, pt-archiver still executes DELETE statements.</p>
<p>Example:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">pt-archiver 
&nbsp;&nbsp;--where "created_at &amp;lt; NOW() - INTERVAL 90 DAY" 
&nbsp;&nbsp;--purge</pre>
<p>Internally, this still produces <strong><span style="color: #339966">DELETE &hellip;</span></strong> instead of <strong><span style="color: #339966">ALTER TABLE &hellip; DROP PARTITION &hellip;</span></strong></p>
<p>This means organizations may lose the major operational benefits of partitioning, or they need to implement custom scripts for managing the selection of rows to copy using pt-archiver and then use DROP PARTITION separately from the tool. That is doable, and to be honest, not too complicated, but why not make pt-archiver aware of partitioning for some specific use cases?</p>
<h1><a class="anchor-link" id=""></a></h1>
<h2>Extending pt-archiver with Pulg-ins<a class="anchor-link" id="extending-pt-archiver-with-pulg-ins"></a></h2>
<p>Fortunately, pt-archiver supports Perl plug-ins.</p>
<p>A plug-in can do plenty of things. Like: inspect runtime conditions, interact with MySQL, override behaviors, and execute custom logic</p>
<p>This gives us an opportunity to implement partition-aware retention handling.</p>
<p>The plug-in can:</p>
<ol>
<li>Inspect partition definitions</li>
<li>Analyze the WHERE condition</li>
<li>Determine which partitions are fully expired</li>
<li>Execute ALTER TABLE DROP PARTITION</li>
<li>Prevent row-by-row DELETE processing</li>
</ol>
<p>This approach combines the scheduling/orchestration power of pt-archiver with the efficiency of partition pruning.</p>
<h3>Plug-in Design<a class="anchor-link" id="plug-in-design"></a></h3>
<p>Our plug-in will:</p>
<ul>
<li>Connect using the pt-archiver DB handle</li>
<li>Inspect INFORMATION_SCHEMA.PARTITIONS</li>
<li>Identify partitions older than the retention cutoff</li>
<li>Issue DROP PARTITION statements</li>
<li>Log actions</li>
<li>Skip DELETE processing</li>
</ul>
<p>Assumptions:</p>
<ul>
<li>The table is RANGE partitioned</li>
<li>Partitions are DATETIME based using the TO_DAYS() function to define ranges</li>
<li>Partition naming convention contains dates</li>
<li>Retention policy aligns with partition boundaries; if the plugin cannot determine a specific boundary, pt-archiver does nothing</li>
</ul>
<h1><a class="anchor-link" id=""></a></h1>
<h2>Full Perl Plug-in for pt-archiver<a class="anchor-link" id="full-perl-plug-in-for-pt-archiver"></a></h2>

<pre class="urvanov-syntax-highlighter-plain-tag">package pt_archiver_partition_drop;

use strict;
use warnings;

sub new {
&nbsp;&nbsp;&nbsp;&nbsp;my ($class, %args) = @_;
&nbsp;&nbsp;&nbsp;&nbsp;my $self = {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dbh&nbsp; &nbsp; &nbsp; &nbsp; =&amp;gt; $args{dbh},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;db &nbsp; &nbsp; &nbsp; &nbsp; =&amp;gt; $args{db},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tbl&nbsp; &nbsp; &nbsp; &nbsp; =&amp;gt; $args{tbl},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;statistics =&amp;gt; {},
&nbsp;&nbsp;&nbsp;&nbsp;};

&nbsp;&nbsp;&nbsp;&nbsp;bless $self, $class;
&nbsp;&nbsp;&nbsp;&nbsp;return $self;
}

sub statistics {
&nbsp;&nbsp;&nbsp;&nbsp;my ($self) = @_;
&nbsp;&nbsp;&nbsp;&nbsp;return $self-&amp;gt;{statistics};
}


sub before_begin {
&nbsp;&nbsp;&nbsp;&nbsp;my ($self) = @_;
 &nbsp;&nbsp;&nbsp;my $dbh = $self-&amp;gt;{dbh} or die "Missing dbh from pt-archivern";
&nbsp;&nbsp;&nbsp;&nbsp;my $db&nbsp; = $self-&amp;gt;{db}&nbsp; or die "Missing db from pt-archiver plugin argsn";
&nbsp;&nbsp;&nbsp;&nbsp;my $tbl = $self-&amp;gt;{tbl} or die "Missing tbl from pt-archiver plugin argsn";
&nbsp;&nbsp;&nbsp;&nbsp;my $where&nbsp; = _get_cmdline_option('where');
&nbsp;&nbsp;&nbsp;&nbsp;my $dryrun = $ENV{PT_PARTITION_DROP_DRY_RUN} ? 1 : 0;

&nbsp;&nbsp;&nbsp;&nbsp;die "Missing --where from original command linen" unless $where;

&nbsp;&nbsp;&nbsp;&nbsp;print "PLUGIN before_begin calledn";
&nbsp;&nbsp;&nbsp;&nbsp;print "DB=$db TABLE=$tbln";
&nbsp;&nbsp;&nbsp;&nbsp;print "WHERE=$wheren";
&nbsp;&nbsp;&nbsp;&nbsp;print "PLUGIN_DRY_RUN=$dryrunn";

&nbsp;&nbsp;&nbsp;&nbsp;my ($column, $cutoff_date) = _parse_where($where);

&nbsp;&nbsp;&nbsp;&nbsp;my $partitions = _get_partitions($dbh, $db, $tbl);

&nbsp;&nbsp;&nbsp;&nbsp;if (!@$partitions) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print "Table `$db`.`$tbl` is not partitioned. Refusing DELETE.n";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit(0);
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;my $partition_expr = $partitions-&amp;gt;[0]-&amp;gt;{expression};
&nbsp;&nbsp;&nbsp;&nbsp;die "Missing PARTITION_EXPRESSIONn"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unless defined $partition_expr &amp;amp;&amp;amp; length $partition_expr;

&nbsp;&nbsp;&nbsp;&nbsp;print "Partition expression: $partition_exprn";

&nbsp;&nbsp;&nbsp;&nbsp;my $cutoff_value = _evaluate_cutoff(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$dbh,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$partition_expr,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$column,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$cutoff_date,
&nbsp;&nbsp;&nbsp;&nbsp;);

&nbsp;&nbsp;&nbsp;&nbsp;print "Cutoff date: $cutoff_daten";
&nbsp;&nbsp;&nbsp;&nbsp;print "Cutoff boundary value: $cutoff_valuen";

&nbsp;&nbsp;&nbsp;&nbsp;my $matched;

&nbsp;&nbsp;&nbsp;&nbsp;for my $p (@$partitions) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;next if !defined $p-&amp;gt;{description};
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;next if uc($p-&amp;gt;{description}) eq 'MAXVALUE';

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ($p-&amp;gt;{description} == $cutoff_value) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$matched = $p;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;last;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}


&nbsp;&nbsp;&nbsp;&nbsp;if (!$matched) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print "No exact partition boundary matches cutoff $cutoff_value. Refusing DELETE.n";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit(0);
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;print "Matched boundary partition: $matched-&amp;gt;{name}, position $matched-&amp;gt;{position}n";

&nbsp;&nbsp;&nbsp;&nbsp;my @drop;

&nbsp;&nbsp;&nbsp;&nbsp;for my $p (@$partitions) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;next if !defined $p-&amp;gt;{description};
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;next if uc($p-&amp;gt;{description}) eq 'MAXVALUE';

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ($p-&amp;gt;{position} &amp;lt;= $matched-&amp;gt;{position}) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;push @drop, $p-&amp;gt;{name};
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print "Eligible for DROP: $p-&amp;gt;{name}, boundary $p-&amp;gt;{description}n";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;if (!@drop) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print "No partitions eligible for DROP. Refusing DELETE.n";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit(0);
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;my $sql = sprintf(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"ALTER TABLE %s.%s DROP PARTITION %s",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_quote_ident($db),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_quote_ident($tbl),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;join(", ", map { _quote_ident($_) } @drop),
&nbsp;&nbsp;&nbsp;&nbsp;);

&nbsp;&nbsp;&nbsp;&nbsp;print "SQL: $sqln";

&nbsp;&nbsp;&nbsp;&nbsp;if ($dryrun) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print "PT_PARTITION_DROP_DRY_RUN enabled. Not executing DROP PARTITION.n";
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;else {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$dbh-&amp;gt;do($sql);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print "Dropped partitions: " . join(", ", @drop) . "n";
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;$self-&amp;gt;{statistics}-&amp;gt;{partitions_dropped} = scalar @drop;

&nbsp;&nbsp;&nbsp;&nbsp;exit(0);
}


sub _parse_where {
&nbsp;&nbsp;&nbsp;&nbsp;my ($where) = @_;

&nbsp;&nbsp;&nbsp;&nbsp;$where =~ s/^s+|s+$//g;

&nbsp;&nbsp;&nbsp;&nbsp;die "Only WHERE format supported: created_at &amp;lt; 'YYYY-MM-DD'n"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unless $where =~ /^`?([A-Za-z0-9_]+)`?s*&amp;lt;s*'(d{4}-d{2}-d{2})'s*$/;

&nbsp;&nbsp;&nbsp;&nbsp;return ($1, $2);
}

sub _evaluate_cutoff {
&nbsp;&nbsp;&nbsp;&nbsp;my ($dbh, $partition_expr, $column, $cutoff_date) = @_;

&nbsp;&nbsp;&nbsp;&nbsp;my $expr = $partition_expr;
&nbsp;&nbsp;&nbsp;&nbsp;$expr =~ s/`//g;

&nbsp;&nbsp;&nbsp;&nbsp;die "Partition expression does not reference column `$column`: $partition_exprn"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unless $expr =~ /bQ$columnEb/i;

&nbsp;&nbsp;&nbsp;&nbsp;$expr =~ s/bQ$columnEb/'$cutoff_date'/ig;

&nbsp;&nbsp;&nbsp;&nbsp;die "Unsafe generated expression: $exprn"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unless $expr =~ /^[A-Za-z0-9_s()+-*/,.'":]+$/;

&nbsp;&nbsp;&nbsp;&nbsp;my $sql = "SELECT $expr";

&nbsp;&nbsp;&nbsp;&nbsp;print "Boundary evaluation SQL: $sqln";

&nbsp;&nbsp;&nbsp;&nbsp;my ($value) = $dbh-&amp;gt;selectrow_array($sql);

&nbsp;&nbsp;&nbsp;&nbsp;die "Cannot evaluate cutoff expression: $sqln"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unless defined $value;

&nbsp;&nbsp;&nbsp;&nbsp;return $value;
}

sub _get_partitions {
&nbsp;&nbsp;&nbsp;&nbsp;my ($dbh, $db, $tbl) = @_;

&nbsp;&nbsp;&nbsp;&nbsp;my $sql = q{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SELECT
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PARTITION_NAME,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PARTITION_DESCRIPTION,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PARTITION_EXPRESSION,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PARTITION_ORDINAL_POSITION
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FROM INFORMATION_SCHEMA.PARTITIONS
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WHERE TABLE_SCHEMA = ?
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AND TABLE_NAME = ?
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AND PARTITION_NAME IS NOT NULL
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ORDER BY PARTITION_ORDINAL_POSITION
&nbsp;&nbsp;&nbsp;&nbsp;};

&nbsp;&nbsp;&nbsp;&nbsp;my $sth = $dbh-&amp;gt;prepare($sql);
&nbsp;&nbsp;&nbsp;&nbsp;$sth-&amp;gt;execute($db, $tbl);
&nbsp;&nbsp;&nbsp;&nbsp;my @partitions;

&nbsp;&nbsp;&nbsp;&nbsp;while (my $row = $sth-&amp;gt;fetchrow_hashref()) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;push @partitions, {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;name&nbsp; &nbsp; &nbsp; &nbsp; =&amp;gt; $row-&amp;gt;{PARTITION_NAME},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;description =&amp;gt; $row-&amp;gt;{PARTITION_DESCRIPTION},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;expression&nbsp; =&amp;gt; $row-&amp;gt;{PARTITION_EXPRESSION},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;position&nbsp; &nbsp; =&amp;gt; $row-&amp;gt;{PARTITION_ORDINAL_POSITION},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;return @partitions;
}


sub _get_cmdline_option {

&nbsp;&nbsp;&nbsp;&nbsp;my ($name) = @_;

&nbsp;&nbsp;&nbsp;&nbsp;my $opt = "--$name";

&nbsp;&nbsp;&nbsp;&nbsp;for (my $i = 0; $i &amp;lt; @ARGV; $i++) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ($ARGV[$i] eq $opt &amp;amp;&amp;amp; defined $ARGV[$i + 1]) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return $ARGV[$i + 1];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ($ARGV[$i] =~ /^Q$optE=(.*)$/) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return $1;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;if (open my $fh, '&amp;lt;', "/proc/$$/cmdline") {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;local $/;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;my $raw = &amp;lt;$fh&amp;gt;;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;close $fh;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;my @cmd = split //, $raw;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for (my $i = 0; $i &amp;lt; @cmd; $i++) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ($cmd[$i] eq $opt &amp;amp;&amp;amp; defined $cmd[$i + 1]) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return $cmd[$i + 1];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ($cmd[$i] =~ /^Q$optE=(.*)$/) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return $1;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;return undef;
}



sub _quote_ident {

&nbsp;&nbsp;&nbsp;&nbsp;my ($ident) = @_;

&nbsp;&nbsp;&nbsp;&nbsp;die "Invalid identifier: $identn"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unless defined $ident &amp;amp;&amp;amp; $ident =~ /^[A-Za-z0-9_]+$/;

&nbsp;&nbsp;&nbsp;&nbsp;return "`$ident`";
}

1;</pre>
<p>Create the file named&nbsp; <b>pt_archiver_partition_drop.pm</b> into the <b>/usr/local/share/perl5</b> path.</p>
<p>Also set the environment variable <b>PERL5LIB</b> to let pt-archiver where to find the Perl package</p>
<pre class="urvanov-syntax-highlighter-plain-tag">export PERL5LIB=/usr/local/share/perl5</pre>

<h1><a class="anchor-link" id=""></a></h1>
<h2>Example Usage<a class="anchor-link" id="example-usage"></a></h2>
<p>First, create the partitioned table events and insert some fake data.</p>
<pre class="urvanov-syntax-highlighter-plain-tag">DROP TABLE IF EXISTS events;


CREATE TABLE events (
&nbsp;&nbsp;id BIGINT NOT NULL,
&nbsp;&nbsp;created_at DATETIME NOT NULL,
&nbsp;&nbsp;payload JSON DEFAULT NULL,
&nbsp;&nbsp;PRIMARY KEY (id, created_at)
)
PARTITION BY RANGE (TO_DAYS(created_at)) (
&nbsp;&nbsp;PARTITION p202604 VALUES LESS THAN (TO_DAYS('2026-05-01')),
&nbsp;&nbsp;PARTITION p202605 VALUES LESS THAN (TO_DAYS('2026-06-01')),
&nbsp;&nbsp;PARTITION p202606 VALUES LESS THAN (TO_DAYS('2026-07-01')),
&nbsp;&nbsp;PARTITION pmax VALUES LESS THAN MAXVALUE
);

INSERT INTO events (id, created_at, payload) VALUES

-- p202604
(1,&nbsp; '2026-04-01 08:00:00', JSON_OBJECT('event', 'login',&nbsp; &nbsp; 'user', 'alice')),
(2,&nbsp; '2026-04-03 09:15:00', JSON_OBJECT('event', 'view', &nbsp; &nbsp; 'page', 'home')),
(3,&nbsp; '2026-04-05 10:30:00', JSON_OBJECT('event', 'click',&nbsp; &nbsp; 'button', 'signup')),
(4,&nbsp; '2026-04-08 11:45:00', JSON_OBJECT('event', 'search', &nbsp; 'term', 'mysql')),
(5,&nbsp; '2026-04-10 12:00:00', JSON_OBJECT('event', 'purchase', 'amount', 100)),
(6,&nbsp; '2026-04-14 13:20:00', JSON_OBJECT('event', 'logout', &nbsp; 'user', 'alice')),
(7,&nbsp; '2026-04-18 14:35:00', JSON_OBJECT('event', 'download', 'file', 'report.pdf')),
(8,&nbsp; '2026-04-22 15:50:00', JSON_OBJECT('event', 'upload', &nbsp; 'file', 'image.png')),
(9,&nbsp; '2026-04-26 16:05:00', JSON_OBJECT('event', 'click',&nbsp; &nbsp; 'button', 'buy')),
(10, '2026-04-30 23:59:59', JSON_OBJECT('event', 'month_end')),

-- p202605

(11, '2026-05-01 00:00:00', JSON_OBJECT('event', 'login',&nbsp; &nbsp; 'user', 'bob')),
(12, '2026-05-03 08:10:00', JSON_OBJECT('event', 'view', &nbsp; &nbsp; 'page', 'pricing')),
(13, '2026-05-06 09:20:00', JSON_OBJECT('event', 'search', &nbsp; 'term', 'percona')),
(14, '2026-05-09 10:30:00', JSON_OBJECT('event', 'purchase', 'amount', 250)),
(15, '2026-05-12 11:40:00', JSON_OBJECT('event', 'logout', &nbsp; 'user', 'bob')),
(16, '2026-05-16 12:50:00', JSON_OBJECT('event', 'download', 'file', 'backup.sql')),
(17, '2026-05-20 13:00:00', JSON_OBJECT('event', 'upload', &nbsp; 'file', 'data.csv')),
(18, '2026-05-24 14:10:00', JSON_OBJECT('event', 'click',&nbsp; &nbsp; 'button', 'subscribe')),
(19, '2026-05-28 15:20:00', JSON_OBJECT('event', 'view', &nbsp; &nbsp; 'page', 'docs')),
(20, '2026-05-31 23:59:59', JSON_OBJECT('event', 'month_end')),

-- p202606

(21, '2026-06-01 00:00:00', JSON_OBJECT('event', 'login',&nbsp; &nbsp; 'user', 'carol')),
(22, '2026-06-03 08:05:00', JSON_OBJECT('event', 'search', &nbsp; 'term', 'partitioning')),
(23, '2026-06-06 09:15:00', JSON_OBJECT('event', 'view', &nbsp; &nbsp; 'page', 'dashboard')),
(24, '2026-06-09 10:25:00', JSON_OBJECT('event', 'purchase', 'amount', 500)),
(25, '2026-06-12 11:35:00', JSON_OBJECT('event', 'logout', &nbsp; 'user', 'carol')),
(26, '2026-06-16 12:45:00', JSON_OBJECT('event', 'login',&nbsp; &nbsp; 'user', 'dave')),
(27, '2026-06-20 13:55:00', JSON_OBJECT('event', 'download', 'file', 'archive.zip')),
(28, '2026-06-24 14:05:00', JSON_OBJECT('event', 'upload', &nbsp; 'file', 'video.mp4')),
(29, '2026-06-28 15:15:00', JSON_OBJECT('event', 'click',&nbsp; &nbsp; 'button', 'checkout')),
(30, '2026-06-30 23:59:59', JSON_OBJECT('event', 'month_end')),

-- pmax
(31, '2026-07-01 00:00:00', JSON_OBJECT('event', 'login',&nbsp; &nbsp; 'user', 'eve')),
(32, '2026-07-05 08:30:00', JSON_OBJECT('event', 'view', &nbsp; &nbsp; 'page', 'future')),
(33, '2026-07-10 09:45:00', JSON_OBJECT('event', 'search', &nbsp; 'term', 'maxvalue')),
(34, '2026-08-01 10:00:00', JSON_OBJECT('event', 'purchase', 'amount', 750)),
(35, '2026-09-01 11:15:00', JSON_OBJECT('event', 'retained_future'));</pre>
<p>&nbsp;</p>
<p>Now you can run the following command to delete all rows before the 1st of May, which, by the way, matches the entire first partition in the table.</p>
<pre class="urvanov-syntax-highlighter-plain-tag">pt-archiver 
&nbsp;&nbsp;--source h=localhost,D=mydb,t=events,m=pt_archiver_partition_drop 
&nbsp;&nbsp;--where "created_at &amp;lt; '2026-05-01'" 
&nbsp;&nbsp;--purge</pre>
<p>&nbsp;</p>
<p>Notice the Perl plugin must be indicated with the <b>m</b> option in the DSN string.</p>
<p>In practice:</p>
<ul>
<li>pt-archiver initializes</li>
<li>The plug-in runs</li>
<li>Partitions are dropped</li>
<li>No DELETE statements are executed</li>
</ul>
<p>Here is what you get from the execution of the above command:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">PLUGIN before_begin called
DB=mydb TABLE=events
WHERE=created_at &amp;lt; '2026-05-01'
PLUGIN_DRY_RUN=0
Partition expression: to_days(`created_at`)
Boundary evaluation SQL: SELECT to_days('2026-05-01')
Cutoff date: 2026-05-01
Cutoff boundary value: 740102
Matched boundary partition: p202604, position 1
Eligible for DROP: p202604, boundary 740102
SQL: ALTER TABLE `mydb`.`events` DROP PARTITION `p202604`
Dropped partitions: p202604</pre>
<p>You can simply verify the table has been managed correctly:</p>
<p><span style="color: #339966">SELECT * FROM mydb.events;</span></p>
<p><span style="color: #339966">SHOW CREATE TABLE mydb.events;</span></p>
<p>&nbsp;</p>
<p>Now TRUNCATE the table and recreate the data and try now to specify the where conditions that match a RANGE that is not the first in the list of the boundaries.</p>
<pre class="urvanov-syntax-highlighter-plain-tag">pt-archiver 
&nbsp;&nbsp;--source h=localhost,D=mydb,t=events,m=pt_archiver_partition_drop 
&nbsp;&nbsp;--where "created_at &amp;lt; '2026-06-01'" 
&nbsp;&nbsp;--purge</pre>
<p>You should get:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">PLUGIN before_begin called
DB=mydb TABLE=events
WHERE=created_at &amp;lt; '2026-06-01'
PLUGIN_DRY_RUN=0
Partition expression: to_days(`created_at`)
Boundary evaluation SQL: SELECT to_days('2026-06-01')
Cutoff date: 2026-06-01
Cutoff boundary value: 740133
Matched boundary partition: p202605, position 2
Eligible for DROP: p202604, boundary 740102
Eligible for DROP: p202605, boundary 740133
SQL: ALTER TABLE `mydb`.`events` DROP PARTITION `p202604`, `p202605`
Dropped partitions: p202604, p202605</pre>
<p>In this case, two partitions have been identified and dropped.</p>
<p>&nbsp;</p>
<p>Truncate the table and recreate the data again. Try now to provide a WHERE condition that does not match any of the boundaries in the RANGE.</p>
<pre class="urvanov-syntax-highlighter-plain-tag">pt-archiver 
&nbsp;&nbsp;--source h=localhost,D=mydb,t=events,m=pt_archiver_partition_drop 
&nbsp;&nbsp;--where "created_at &amp;lt; '2026-04-25'" 
&nbsp;&nbsp;--purge</pre>
<p>&nbsp;</p>
<p>You get the following:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">PLUGIN before_begin called
DB=mydb TABLE=events
WHERE=created_at &amp;lt; '2026-04-25'
PLUGIN_DRY_RUN=0
Partition expression: to_days(`created_at`)
Boundary evaluation SQL: SELECT to_days('2026-04-25')
Cutoff date: 2026-04-25
Cutoff boundary value: 740096
No exact partition boundary matches cutoff 740096. Refusing DELETE.</pre>
<p>As expected, the tool now refuses to execute anything if it doesn&rsquo;t find an exact match.</p>
<p>&nbsp;</p>
<h2>Operational Benefits<a class="anchor-link" id="operational-benefits"></a></h2>
<p>This approach provides major advantages.</p>
<p>Dropping partitions is vastly faster than deleting rows, and minimal binary logging is needed, compared to billions of row deletes. There is no massive transactional overhead for managing undo logs and purging. You get then a better InnoDB Buffer Pool stability because of less page churn.</p>
<p>In the end, retention jobs are completed quickly and consistently in a predictable way and at the minimal cost.</p>
<p>&nbsp;</p>
<h2>Important Caveats<a class="anchor-link" id="important-caveats"></a></h2>
<h3>Partition Boundaries Must Match Retention Policy<a class="anchor-link" id="partition-boundaries-must-match-retention-policy"></a></h3>
<p>If partitions contain mixed retention windows, DROP PARTITION may remove too much data. For this reason, ensure correct partition design.</p>
<p>Recommended:</p>
<ul>
<li>daily partitions</li>
<li>weekly partitions</li>
<li>monthly partitions</li>
</ul>
<p>aligned with business retention requirements.</p>
<h3>Metadata Locks<a class="anchor-link" id="metadata-locks"></a></h3>
<p><span style="color: #339966">ALTER TABLE DROP PARTITION</span> still acquires metadata locks.</p>
<p>Test carefully in production.</p>
<h3>Backup Awareness<a class="anchor-link" id="backup-awareness"></a></h3>
<p>Ensure dropped partitions are no longer needed before removal or use pt-archiver to also copy the data into a remote server or dump the data into a CSV file before running the DROP PARTITION.</p>
<p>&nbsp;</p>
<h2>Possible Enhancements<a class="anchor-link" id="possible-enhancements"></a></h2>
<p>The plug-in can be extended further.</p>
<p>Potential improvements:</p>
<ul>
<li>Support for daily partitions</li>
<li>Support for UNIX timestamp partitions</li>
<li>Dry-run reporting</li>
<li>Automatic partition creation</li>
<li>Push Slack notifications</li>
<li>Export Prometheus metrics</li>
<li>Safety checks for replicas</li>
<li>GTID-aware orchestration</li>
<li>Integration with pt-online-schema-change workflows</li>
</ul>
<p>These are just some ideas I had meanwhile doing my tests. What you can do by implementing a Perl plugin is only limited by your imagination and your real needs.</p>
<h1><a class="anchor-link" id=""></a></h1>
<h2>Conclusion<a class="anchor-link" id="conclusion"></a></h2>
<p>pt-archiver remains an excellent tool for implementing retention policies and archival workflows.</p>
<p>However, DELETE-based purging becomes increasingly expensive at scale, even with proper indexing and chunked processing.</p>
<p>For large time-series or historical datasets, RANGE partitioning is often a dramatically superior strategy.</p>
<p>The challenge is that pt-archiver does not natively leverage partition-level operations.</p>
<p>Fortunately, its Perl plug-in architecture allows advanced users to extend its behavior and implement partition-aware cleanup logic.</p>
<p>By combining:</p>
<ul>
<li>pt-archiver orchestration</li>
<li>MySQL RANGE partitioning</li>
<li>Custom Perl plug-ins</li>
</ul>
<p>Organizations can achieve:</p>
<ul>
<li>Faster retention enforcement</li>
<li>Lower operational overhead</li>
<li>Smaller replication impact</li>
<li>Dramatically improved scalability</li>
</ul>
<p>For large MySQL deployments, this hybrid approach can turn multi-hour purge operations into near-instant metadata operations.</p>
<p>The use case presented in this article is limited to a specific scenario, but you can reuse it or customize it if you have a different kind of RANGE partitioning, for example, not using TO_DAYS().</p>
<p>Take this as just an example of how you can extend pt-archiver. What you can do for real is driven by your needs and/or only limited by your imagination.</p>
<p>More info about extending pt-archiver:<br>
<a href="https://docs.percona.com/percona-toolkit/pt-archiver.html#extending">https://docs.percona.com/percona-toolkit/pt-archiver.html#extending</a></p>
<p>&nbsp;</p>
<p>The post <a href="https://www.percona.com/blog/extending-pt-archiver-with-a-partition-aware-plug-in-for-fast-retention-policy-enforcement/">Extending pt-archiver with a Partition-Aware Plug-in for Fast Retention Policy Enforcement</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/extending-pt-archiver-with-a-partition-aware-plug-in-for-fast-retention-policy-enforcement/">Extending pt-archiver with a Partition-Aware Plug-in for Fast Retention Policy Enforcement</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Java Connector 3.5.9, 3.4.3, 3.3.5, and 2.7.14 now available</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/mariadb-java-connector-3-5-9-3-4-3-3-3-5-and-2-7-14-now-available/" />
      <id>https://mariadb.com/resources/blog/mariadb-java-connector-3-5-9-3-4-3-3-3-5-and-2-7-14-now-available/</id>
      <updated>2026-06-15T18:14:03+00:00</updated>
      <author><name>Daniel Bartholomew</name></author>
      <summary type="html"><![CDATA[<p>MariaDB is pleased to announce the immediate availability of the MariaDB Connector/J 3.5.9, 3.4.3, 3.3.5, and 2.7.14 releases. Download Now […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-java-connector-3-5-9-3-4-3-3-3-5-and-2-7-14-now-available/">MariaDB Java Connector 3.5.9, 3.4.3, 3.3.5, and 2.7.14 now available</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB is pleased to announce the immediate availability of the MariaDB Connector/J 3.5.9, 3.4.3, 3.3.5, and 2.7.14 releases. Download Now Notable items in this release include: Notable items in this release include: Notable items in this release include: Notable items in this release include: See&hellip;</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-java-connector-3-5-9-3-4-3-3-3-5-and-2-7-14-now-available/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-java-connector-3-5-9-3-4-3-3-3-5-and-2-7-14-now-available/">MariaDB Java Connector 3.5.9, 3.4.3, 3.3.5, and 2.7.14 now available</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Group Replication VS Percona XtraDB Cluster: The True Cost of Consistency</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/group-replication-vs-percona-xtradb-cluster-the-true-cost-of-consistency/" />
      <id>https://www.percona.com/blog/group-replication-vs-percona-xtradb-cluster-the-true-cost-of-consistency/</id>
      <updated>2026-06-15T06:58:17+00:00</updated>
      <author><name>Marco Tusa</name></author>
      <summary type="html"><![CDATA[<p>Overview When building high-availability MySQL environments, the choice between MySQL Group Replication (GR) and Percona XtraDB Cluster (PXC) often comes down to how they handle the eternal database dilemma: data consistency versus performance.        While both provide “synchronous-like” replication, they approach the problem of stale reads—reading data that has been committed on one node but not … Continued<br />
The post Group Replication VS Percona XtraDB Cluster: The True Cost of Consistency appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/group-replication-vs-percona-xtradb-cluster-the-true-cost-of-consistency/">Group Replication VS Percona XtraDB Cluster: The True Cost of Consistency</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<h2><span style="font-weight: 400">Overview</span><a class="anchor-link" id="overview"></a></h2>
<p><span style="font-weight: 400">When building high-availability MySQL environments, the choice between MySQL Group Replication (GR) and Percona XtraDB Cluster (PXC) often comes down to how they handle the eternal database dilemma: data consistency versus performance.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img decoding="async" loading="lazy" class=" wp-image-49842 alignright" src="https://www.percona.com/wp-content/uploads/2026/06/dolphin_vs_goath_small.jpg" alt="" width="546" height="273"></span></p>
<p><span style="font-weight: 400">While both provide &ldquo;synchronous-like&rdquo; replication, they approach the problem of </span><b>stale reads</b><span style="font-weight: 400">&mdash;reading data that has been committed on one node but not yet applied on another&mdash;in distinct ways. Understanding these differences, and the performance penalties associated with fixing them, is critical for any production environment.</span></p>
<h3><span style="font-weight: 400">Technology Overviews</span><a class="anchor-link" id="technology-overviews"></a></h3>
<p><b>MySQL Group Replication (GR)</b></p>
<p><span style="font-weight: 400">Group Replication is the native, albeit more recent, high-availability solution built by Oracle for MySQL. It is based on a distributed state machine architecture and uses the Paxos consensus protocol.</span></p>
<ul>
<li style="font-weight: 400"><b>Mechanism:</b><span style="font-weight: 400"> When a transaction is committed, it is sent to all group members. The members must agree (consensus) on the order of transactions. Once a majority agrees, the transaction is &ldquo;certified&rdquo; and committed on the originator.</span></li>
<li style="font-weight: 400"><b>Replication Type:</b> <i><span style="font-weight: 400">Virtually synchronous.</span></i><span style="font-weight: 400"> The consensus ensures the data is received and ordered across nodes, but the actual applying of the data to the database happens asynchronously in the background.</span></li>
</ul>
<p><b>Percona XtraDB Cluster (PXC)</b></p>
<p><span style="font-weight: 400">PXC is an open-source enterprise solution based on Percona Server for MySQL and the Galera Replication library, which is the first and most mature virtually synchronous solution for MySQL.</span></p>
<ul>
<li style="font-weight: 400"><b>Mechanism:</b><span style="font-weight: 400"> When a node commits a transaction, it sends it to all other members of the Primary component (active group). All nodes must certify the transaction (check for conflicts), this is done on each node in the cluster, including the node that originates the write-set, before the originating node can finalize the commit.</span></li>
<li style="font-weight: 400"><b>Replication Type:</b> <i><span style="font-weight: 400">Strictly synchronous (up to the certification level)</span></i><span style="font-weight: 400">, asynchronous afterward. If the certification test fails, the node drops the write-set and the cluster rolls back the original transaction. If the test succeeds, however, the transaction commits and the write-set is applied to the rest of the cluster.</span></li>
</ul>
<h2><span style="font-weight: 400">The Battle Against &ldquo;Stale Reads&rdquo;: Why It Matters</span><a class="anchor-link" id="the-battle-against-stale-reads-why-it-matters"></a></h2>
<p><span style="font-weight: 400">The most critical distinction for developers is whether a SELECT query on </span><b>Node B</b><span style="font-weight: 400"> will immediately see the INSERT just performed on </span><b>Node A</b><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">In a distributed system, there is a microsecond-to-millisecond gap between a transaction being globally ordered (everyone knows it happened) and being locally applied (the data is physically readable in the table). Reading executed on a secondary during this gap results in a </span><b>stale read</b><span style="font-weight: 400">.</span></p>
<h3><span style="font-weight: 400">Why is avoiding stale reads so critical?</span><a class="anchor-link" id="why-is-avoiding-stale-reads-so-critical"></a></h3>
<p><span style="font-weight: 400">While a stale read might just mean a user temporarily sees their old profile picture after updating it, in many business cases, it breaks the application&rsquo;s core logic:</span></p>
<ol>
<li style="font-weight: 400"><b>Financial Transactions:</b><span style="font-weight: 400"> A user deposits $100 on the Primary node and immediately refreshes their balance page, which reads from a Replica. If the read is stale, the balance hasn&rsquo;t updated. The user panics, thinking their money is lost.</span></li>
<li style="font-weight: 400"><b>E-commerce &amp; Inventory:</b><span style="font-weight: 400"> A customer buys the last item in stock. The next user immediately loads the product page. A stale read tells the second user the item is still available, leading to a cancelled order and a frustrated customer.</span></li>
<li style="font-weight: 400"><b>Security &amp; Access:</b><span style="font-weight: 400"> A user changes their password or updates a critical permission. If the next authentication request hits a node lagging by just a fraction of a second, their valid login might be rejected, or a revoked session might still be active.</span></li>
</ol>
<p><span style="font-weight: 400">To prevent these scenarios, we must tell the database to enforce strict consistency. But how do GR and PXC handle this, and what does it cost?</span></p>
<h3><span style="font-weight: 400">Consistency Controls Comparison</span><a class="anchor-link" id="consistency-controls-comparison"></a></h3>
<p><span style="font-weight: 400">Both Group Replication and Percona XtraDB Cluster provide built-in mechanisms to enforce consistency and eliminate stale reads when your application demands it. However, they approach this problem using entirely different variables and distinct levels of granularity. The table below breaks down the specific controls each technology offers, highlighting exactly what it takes to force a node to serve fresh data.</span></p>
<table>
<thead>
<tr>
<th><b>Feature</b></th>
<th><b>MySQL Group Replication</b></th>
<th><b>Percona XtraDB Cluster</b></th>
</tr>
</thead>
<tbody>
<tr>
<td><b>Default Behavior</b></td>
<td><span style="font-weight: 400">Reads on secondaries may be stale because the applier thread might be lagging after consensus.</span></td>
<td><span style="font-weight: 400">Reads on secondaries may be stale due to asynchronous background applying.</span></td>
</tr>
<tr>
<td><b>Stale Read Fix</b></td>
<td><span style="font-weight: 400">Uses the group_replication_consistency variable.</span></td>
<td><span style="font-weight: 400">Uses the wsrep-sync-wait variable.</span></td>
</tr>
<tr>
<td><b>Consistency Levels</b></td>
<td><span style="font-weight: 400">Offers EVENTUAL, BEFORE, AFTER, and BEFORE_AND_AFTER.</span></td>
<td><span style="font-weight: 400">Offers granular levels from 0 (default, no checks) up to 7 (checks on all READ, UPDATE, DELETE, INSERT, and REPLACE statements).</span></td>
</tr>
<tr>
<td><b>The Fix</b></td>
<td><span style="font-weight: 400">Setting to AFTER ensures the next read is fresh.</span></td>
<td><span style="font-weight: 400">Setting to 7 ensures we have a comparable scenario with GR. However in PXC setting wsrep_sync_wait = 1 will be enough to avoid stale reads.</span></td>
</tr>
</tbody>
</table>
<h2><span style="font-weight: 400">The True Cost of Being Consistent</span><a class="anchor-link" id="the-true-cost-of-being-consistent"></a></h2>
<p><span style="font-weight: 400">If we know stale reads are bad, why don&rsquo;t we just enforce strict consistency everywhere?&nbsp;</span></p>
<p><span style="font-weight: 400">An image can help to understand:</span></p>
<p><img decoding="async" loading="lazy" class="wp-image-49841 alignnone" src="https://www.percona.com/wp-content/uploads/2026/06/dirty_comparative2-1024x566.png" alt="" width="695" height="384"></p>
<p><span style="font-weight: 400">Because in distributed databases, </span><b>consistency is incredibly expensive.</b><span style="font-weight: 400"> To test this, we used a 3-node internal lab environment to run a Sysbench-based TPC-C derivative test (50/50 read/write split, running for 600 seconds, scaling from 1 to 1024 threads).</span></p>
<p><span style="font-weight: 400">You can find the detailed machine specifications </span><a href="https://github.com/Tusamarco/blogs/blob/master/testmachine/chaos_test_machine.txt"><b>here</b></a><span style="font-weight: 400">. The benchmarks were executed using a TPC-C derivative test based on </span><a href="https://github.com/Tusamarco/sysbench-tpcc"><b>sysbench</b></a><span style="font-weight: 400">. Finally&mdash;and crucially&mdash;you can review the </span><a href="https://github.com/Tusamarco/blogs/blob/master/testmachine/ps_vs_pxc_configuration.md"><b>configuration files</b></a><span style="font-weight: 400"> used for the tests. I maintained the same baseline MySQL configuration across the board, only adjusting the parameters specific to each replication technology.</span></p>
<p>&nbsp;</p>
<h3><span style="font-weight: 400">Scenario 1: Default (Relaxed) Consistency</span><a class="anchor-link" id="scenario-1-default-relaxed-consistency"></a></h3>
<p><i><span style="font-weight: 400">(GR = EVENTUAL, PXC = wsrep-sync-wait 0)</span></i></p>
<p><span style="font-weight: 400">I want to remind, that MySQL CE and Percona Server are running using Group Replication, while PXC is using galera.</span></p>
<p><span style="font-weight: 400">With default settings, both systems allow stale reads.</span></p>
<p><img decoding="async" loading="lazy" class="alignnone size-large wp-image-49838" src="https://www.percona.com/wp-content/uploads/2026/06/CHAOS_tpcc_PXC_VS_PS_eventual_run_tpcc_RepeatableRead-1024x597.png" alt="" width="1024" height="597"></p>
<p><img decoding="async" loading="lazy" class="alignnone size-large wp-image-49837" src="https://www.percona.com/wp-content/uploads/2026/06/CHAOS_tpcc_PXC_VS_PS_eventual_run_tpcc_ReadCommitted-1024x597.png" alt="" width="1024" height="597"></p>
<p><span style="font-weight: 400">Both technologies scales well up to 128 threads:</span></p>
<ul>
<li style="font-weight: 400"><b>Group Replication</b><span style="font-weight: 400"> performs exceptionally well, handling up to 15K operations/sec before dropping off after 128 threads.</span></li>
<li style="font-weight: 400"><b>PXC (Galera)</b><span style="font-weight: 400"> is slightly less efficient at peak but scales very nicely and predictably.</span></li>
</ul>
<p><span style="font-weight: 400">At this level, the lag between the moment of commit and the moment the server returns the answer is minimal. But we are entirely exposed to stale reads.</span></p>
<h3><span style="font-weight: 400">Scenario 2: Enforced Consistency (The Cost)</span><a class="anchor-link" id="scenario-2-enforced-consistency-the-cost"></a></h3>
<p><i><span style="font-weight: 400">(GR = AFTER, PXC = wsrep-sync-wait 7)</span></i></p>
<p><span style="font-weight: 400">When we configure the servers to prevent stale reads, the systems must wait for transactions to be fully applied before returning a read. This is where the architectural differences become glaringly apparent:</span></p>
<p><img decoding="async" loading="lazy" class="alignnone size-large wp-image-49835" src="https://www.percona.com/wp-content/uploads/2026/06/CHAOS_tpcc_PXC_VS_PS_after_run_tpcc_ReadCommitted-1024x597.png" alt="" width="1024" height="597"> <img decoding="async" loading="lazy" class="alignnone size-large wp-image-49836" src="https://www.percona.com/wp-content/uploads/2026/06/CHAOS_tpcc_PXC_VS_PS_after_run_tpcc_RepeatableRead-1024x597.png" alt="" width="1024" height="597"></p>
<ul>
<li style="font-weight: 400"><b>PXC (Galera):</b><span style="font-weight: 400"> Performance drops but not too much from a peak of ~9K ops/sec (in the previous test)&nbsp; to roughly </span><b>~8.5K ops/sec</b><span style="font-weight: 400">. This is a hit but not huge and the database remains highly functional and stable.</span></li>
<li style="font-weight: 400"><b>Group Replication:</b><span style="font-weight: 400"> Performance catastrophically drops from ~15K ops/sec (in the previous test) to a staggering </span><b>~3.8K ops/sec</b><span style="font-weight: 400">.</span></li>
</ul>
<h3><span style="font-weight: 400">This is the crucial takeaway</span><a class="anchor-link" id="this-is-the-crucial-takeaway"></a></h3>
<p><span style="font-weight: 400">Enforcing strict consistency in Group Replication results in a massive ~75% performance penalty. The latency between the commit and the server response increases significantly compared to PXC.&nbsp;</span></p>
<h2><span style="font-weight: 400">The intermediate way</span><a class="anchor-link" id="the-intermediate-way"></a></h2>
<p><span style="font-weight: 400">There is another approach which is to inject the higher consistency only when it is really needed.</span></p>
<p><b>The Solution: Session-Level Consistency</b><span style="font-weight: 400"> You do not need, and should not use, full consistency at the global level for general cases. Instead, force consistency </span><i><span style="font-weight: 400">only when and where it is critical</span></i><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">While for Group Replication there is no support for SQL injection hints like SELECT /*+ SET_VAR(&hellip;) */, you can enforce this at the session level right before a critical read:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">SET SESSION group_replication_consistency = 'AFTER';
-- OR for PXC:
SET SESSION wsrep_sync_wait = 7;</pre>
<p>&nbsp;</p>
<p><span style="font-weight: 400">To note that&nbsp; PXC offers more flexibility and you can use hints:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">select /*+ SET_VAR(wsrep_sync_wait=7) */ @@session.wsrep_sync_wait ,@@global.wsrep_sync_wait;
+---------------------------+--------------------------+
| @@session.wsrep_sync_wait | @@global.wsrep_sync_wait |
+---------------------------+--------------------------+
|                         7 |                        0 |
+---------------------------+--------------------------+</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">By isolating these variables to specific sessions (like the immediate redirect after a password change or a checkout process), you ensure data integrity exactly where the business requires it, while allowing the rest of your application to enjoy the high-speed performance of relaxed consistency.&nbsp;</span></p>
<p><img decoding="async" loading="lazy" class="alignnone size-large wp-image-49839" src="https://www.percona.com/wp-content/uploads/2026/06/CHAOS_tpcc_PXC_VS_PS_partial_run_tpcc_ReadCommitted-1024x597.png" alt="" width="1024" height="597"> <img decoding="async" loading="lazy" class="alignnone size-large wp-image-49840" src="https://www.percona.com/wp-content/uploads/2026/06/CHAOS_tpcc_PXC_VS_PS_partial_run_tpcc_RepeatableRead-1024x597.png" alt="" width="1024" height="597"></p>
<p><b>PXC:</b><span style="font-weight: 400"> The performance drop is minimal and the solution is able to provide a consistent delivery with nice scalability up to 256 threads.</span></p>
<p><b>Group Replication: </b><span style="font-weight: 400">The solution suffers from a significant drop, not as if we set the AFTER condition at global level, but still we see a drop of ~52%.&nbsp;</span></p>
<p><span style="font-weight: 400">Comparing the two solutions we can see that PXC is able to deal with the additional requested consistency better.&nbsp;</span></p>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">Additional differences</span><a class="anchor-link" id="additional-differences"></a></h2>
<p><span style="font-weight: 400">But these are not the only differences we can immediately see.</span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400">Performing a comparison about resources utilization, we can see that while both solutions </span><i><span style="font-weight: 400">move</span></i><span style="font-weight: 400"> the same amount of data as IO operations:</span></p>
<p><img decoding="async" loading="lazy" class="alignnone size-large wp-image-49846" src="https://www.percona.com/wp-content/uploads/2026/06/pxc_vs_gr_disk_util-1024x500.png" alt="" width="1024" height="500"></p>
<p>&nbsp;</p>
<p><img decoding="async" loading="lazy" class="alignnone size-large wp-image-49847" src="https://www.percona.com/wp-content/uploads/2026/06/pxc_vs_gr_memory_used-1024x514.png" alt="" width="1024" height="514"></p>
<p><span style="font-weight: 400">Yes, for exactly the same load and traffic Group Replication </span><i><span style="font-weight: 400">consumes</span></i><span style="font-weight: 400"><strong> 8GB</strong> more than PXC, which in this environment represents 26% memory more, over total available.</span></p>
<p><img decoding="async" loading="lazy" class="alignnone size-large wp-image-49845" src="https://www.percona.com/wp-content/uploads/2026/06/pxc_vs_gr_cpu-1024x507.png" alt="" width="1024" height="507"></p>
<p><span style="font-weight: 400">Cost that is reflected also as CPU utilization.</span></p>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">Conclusion: How to Survive the Cost</span><a class="anchor-link" id="conclusion-how-to-survive-the-cost"></a></h2>
<p><span style="font-weight: 400">How impactful is enforcing strict consistency at a global level in a production environment? </span><b>Massively.</b><span style="font-weight: 400"> If you blindly enforce strict consistency globally without understanding your architecture, you will decimate your database throughput. Here is the reality of how the two solutions handle that tax:</span></p>
<ul>
<li style="font-weight: 400"><b>The Group Replication Reality:</b><span style="font-weight: 400"> By default (using </span><span style="font-weight: 400">EVENTUAL</span><span style="font-weight: 400"> consistency), MySQL Group Replication behaves essentially as semi-synchronous replication paired with an automated topology manager </span><i><span style="font-weight: 400">(see <a href="https://www.percona.com/blog/the-failover-brownout-rethinking-high-availability-in-mysql-group-replication/">The Failover Brownout: Rethinking High Availability in MySQL Group Replication</a>)</span></i><span style="font-weight: 400">. The Primary is allowed to forge ahead and serve traffic even if the Secondaries are lagging significantly behind. The moment you demand strict consistency, the Primary is violently tethered back to the rest of the cluster, and its performance drops off a cliff as it waits for the slowest node.</span></li>
<li style="font-weight: 400"><b>The PXC Advantage:</b><span style="font-weight: 400"> Percona XtraDB Cluster (PXC) absorbs the &ldquo;consistency penalty&rdquo; much more gracefully. While varying consistency levels exist in PXC, adjusting them does not cause the same dramatic throughput shock seen in MGR. This is because PXC enforces a virtually synchronous, high-consistency baseline from the start. It simply does not allow the node receiving writes to deviate too far from the rest of the cluster. You pay a baseline performance tax upfront, but in exchange, you get guaranteed, ironclad High Availability out of the box.</span></li>
</ul>
<p><b>The Final Verdict</b><span style="font-weight: 400"> Modifying consistency values at the global server level should only be done after rigorous load testing and a complete understanding of the performance tax you are about to pay.</span></p>
<p><span style="font-weight: 400">Ultimately, it comes down to choosing the right tool for your specific SLA:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">If your architecture demands a true, virtually synchronous solution with strict High Availability out of the box, </span><b>PXC</b><span style="font-weight: 400"> is the purpose-built engine for the job.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">If you are looking for a highly automated, semi-synchronous solution, </span><b>Group Replication</b><span style="font-weight: 400"> delivers excellent default performance&mdash;but tuning it to mimic PXC&rsquo;s strict consistency will cost you heavily in throughput.</span></li>
</ul>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">References</span><a class="anchor-link" id="references"></a></h2>
<p><a href="https://mariadb.com/docs/galera-cluster/galera-architecture/certification-based-replication"><span style="font-weight: 400">https://www.google.com/url?q=https://mariadb.com/docs/galera-cluster/galera-architecture/certification-based-replication&amp;sa=D&amp;source=docs&amp;ust=1777342808813139&amp;usg=AOvVaw3SAf2g7NO9d681ZJ0VVEMB</span></a></p>
<p><a href="https://docs.percona.com/percona-xtradb-cluster/5.7/wsrep-system-index.html#wsrep_sync_wait"><span style="font-weight: 400">https://docs.percona.com/percona-xtradb-cluster/5.7/wsrep-system-index.html#wsrep_sync_wait</span></a></p>
<p>The post <a href="https://www.percona.com/blog/group-replication-vs-percona-xtradb-cluster-the-true-cost-of-consistency/">Group Replication VS Percona XtraDB Cluster: The True Cost of Consistency</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/group-replication-vs-percona-xtradb-cluster-the-true-cost-of-consistency/">Group Replication VS Percona XtraDB Cluster: The True Cost of Consistency</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>The Failover Brownout: Rethinking High Availability in MySQL Group Replication</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/the-failover-brownout-rethinking-high-availability-in-mysql-group-replication/" />
      <id>https://www.percona.com/blog/the-failover-brownout-rethinking-high-availability-in-mysql-group-replication/</id>
      <updated>2026-06-15T06:57:24+00:00</updated>
      <author><name>Marco Tusa</name></author>
      <summary type="html"><![CDATA[<p>It is time to talk again about Flow control and group replication. This time with a special eye on the use of Group Replication in the Kubernetes context. In this article we will dig a bit on how it works and what are the various side effects.    The problem Recently I was refining the … Continued<br />
The post The Failover Brownout: Rethinking High Availability in MySQL Group Replication appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/the-failover-brownout-rethinking-high-availability-in-mysql-group-replication/">The Failover Brownout: Rethinking High Availability in MySQL Group Replication</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">It is time to talk again about Flow control and group replication. This time with a special eye on the use of Group Replication in the Kubernetes context. In this article we will dig a bit on how it works and what are the various side effects.&nbsp;</span></p>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">The problem</span><a class="anchor-link" id="the-problem"></a></h2>
<p><span style="font-weight: 400">Recently I was refining the calculation I use in the </span><a href="https://github.com/Tusamarco/mysqloperatorcalculator"><span style="font-weight: 400">MySQL calculator for Operator</span></a><span style="font-weight: 400"> given I was constantly encountering a very serious problem with the Percona Server Operator.</span></p>
<p><span style="font-weight: 400">The problem is that when the deployment was/is serving a high level of traffic, it will, no matter what, end up in getting OMMKill by the K8 system.&nbsp;</span></p>
<p><span style="font-weight: 400">This because the pod was gradually consuming more and more memory, reaching the memory limit set in the CR specification.&nbsp;</span></p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">Now let me clarify a few things, to get straight to the facts.</span></p>
<p><span style="font-weight: 400">Kubernetes itself does not OOMKill a pod for hitting its memory limit, the mechanism works as described below with mention on how Working Set Size (WSS) is calculated, and how OOMKills are triggered, and in the resource sections, the links to the official documentation and source code.</span></p>
<p>&nbsp;</p>
<h3><span style="font-weight: 400">1. The Reality of OOMKills vs. Kubelet Evictions</span><a class="anchor-link" id="1-the-reality-of-oomkills-vs-kubelet-evictions"></a></h3>
<p><span style="font-weight: 400">It is crucial to distinguish between what the Linux kernel does and what Kubernetes does:</span></p>
<ul>
<li style="font-weight: 400"><b>OOMKilled (Exit Code 137):</b><span style="font-weight: 400"> This is executed entirely by the </span><b>Linux kernel&rsquo;s OOM Killer</b><span style="font-weight: 400">, not Kubernetes. When we set a memory limit in our Pod spec, Kubernetes translates that into a Linux cgroup constraint (</span><span style="font-weight: 400">memory.limit_in_bytes</span><span style="font-weight: 400"> for cgroups v1, or </span><span style="font-weight: 400">memory.max</span><span style="font-weight: 400"> for cgroups v2). If our container attempts to allocate more memory than this hard limit, and the kernel cannot reclaim any page cache (like inactive files), the kernel directly intervenes and terminates the process.</span></li>
<li style="font-weight: 400"><b>Node-Pressure Evictions:</b><span style="font-weight: 400"> This is where Kubernetes actively observes memory. The </span><span style="font-weight: 400">kubelet</span><span style="font-weight: 400"> monitors the </span><span style="font-weight: 400">working_set_bytes</span><span style="font-weight: 400"> metric to protect the </span><i><span style="font-weight: 400">node</span></i><span style="font-weight: 400"> from running out of memory. If the node&rsquo;s memory drops below an eviction threshold, Kubernetes will actively evict pods to prevent the kernel from initiating a system-wide OOM kill.</span></li>
</ul>
<h3><span style="font-weight: 400">2. How Working Set Size (WSS) is Calculated for the container</span><a class="anchor-link" id="2-how-working-set-size-wss-is-calculated-for-the-container"></a></h3>
<p><span style="font-weight: 400">Kubernetes monitors container memory via </span><b>cAdvisor</b><span style="font-weight: 400">, which is integrated directly into the </span><span style="font-weight: 400">kubelet</span><span style="font-weight: 400">. cAdvisor calculates the Working Set Size by taking the total memory usage and subtracting the inactive file cache (memory that the kernel can easily reclaim if it faces memory pressure).</span></p>
<p><span style="font-weight: 400">Because active file caches and anonymous memory (like our application&rsquo;s heap) cannot be easily evicted, this working set metric is the most accurate representation of the memory your container is forcing the system to hold.</span></p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">The Calculation &amp; cgroups Evolution The core mathematical calculation is </span><i><span style="font-weight: 400">Memory Usage</span></i><i><span style="font-weight: 400"> &ndash; </span></i><i><span style="font-weight: 400">Inactive File Cache</span></i><span style="font-weight: 400">, but </span><i><span style="font-weight: 400">how</span></i><span style="font-weight: 400"> cAdvisor fetches this data from the Linux kernel depends entirely on your node&rsquo;s cgroup version. Modern cAdvisor relies heavily on the </span><span style="font-weight: 400">opencontainers/runc/libcontainer</span><span style="font-weight: 400"> library to read these raw cgroup files:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">cgroups v1: cAdvisor starts with the raw usage from </span><span style="font-weight: 400">memory.usage_in_bytes</span><span style="font-weight: 400"> and subtracts the reclaimable cache found under the </span><span style="font-weight: 400">total_inactive_file</span><span style="font-weight: 400"> key.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">cgroups v2 (Unified): cAdvisor starts with the raw usage from </span><span style="font-weight: 400">memory.current</span><span style="font-weight: 400"> and subtracts the reclaimable cache found under the </span><span style="font-weight: 400">inactive_file</span><span style="font-weight: 400"> key.</span></li>
</ul>
<p>&nbsp;</p>
<p><span style="font-weight: 400">The Underlying Code Logic While older versions used a static </span><span style="font-weight: 400">setMemoryStats</span><span style="font-weight: 400"> function, modern Kubernetes branches handle this dynamically. The logic executes the following flow before reporting back to the </span><span style="font-weight: 400">kubelet</span><span style="font-weight: 400">:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Detects Version: It identifies whether the node runs cgroups v1 or v2 to determine the correct inactive file key name.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Fetch Usage: It pulls the raw memory usage from the container.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Subtract Cache: It looks up the inactive file value and safely subtracts it from the usage (including a safeguard to ensure the working set never drops below zero).</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Report Metric: It sets this final calculated value as </span><span style="font-weight: 400">container_memory_working_set_bytes</span><span style="font-weight: 400">, which the </span><span style="font-weight: 400">kubelet</span><span style="font-weight: 400"> then uses to decide if the node is under memory pressure.</span></li>
</ol>
<h2><span style="font-weight: 400">Back to us&nbsp;</span><a class="anchor-link" id="back-to-us"></a></h2>
<p><span style="font-weight: 400">At the end the point is that if our pod reaches the limit and we ARE NOT using the new </span><a href="https://docs.google.com/document/d/1WSoJxaAPMP4tdiT_-U4YwgoHBI8zKJ0Hw-y6iUXJQBc/edit#bookmark=id.59zqn83hsx1p"><span style="font-weight: 400">swap feature</span></a><span style="font-weight: 400"> existing in Kubernetes, our pod will be brutally killed, and in 99% of the cases our production will suffer a lot. !Ops spoiler!</span></p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">To clearly understand what was causing the issue about this memory consumption and having my calculator fail, I started to collect the information about the memory usage in MySQL itself.</span></p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">SELECT EVENT_NAME,CURRENT_NUMBER_OF_BYTES_USED / 1024 / 1024 AS current_usage_mb FROM performance_schema.memory_summary_global_by_event_name WHERE EVENT_NAME like &lsquo;memory/%&rsquo; and EVENT_NAME not like &lsquo;memory/performance%&rsquo;&nbsp; order by current_usage_mb desc limit 25;</span></p>
<p><span style="font-weight: 400">Which will give you and output like this:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">+---------------------------------------+------------------+
| EVENT_NAME                            | current_usage_mb |
+---------------------------------------+------------------+
| memory/innodb/buf_buf_pool            |   46398.92578125 |
| memory/group_rpl/GCS_XCom::xcom_cache |    1066.66179943 |
| memory/group_rpl/certification_info   |      92.45250702 |
| memory/innodb/log_buffer_memory       |      64.00096130 |
| memory/sql/TABLE                      |      49.90627003 |
| memory/innodb/memory                  |      34.68734741 |
| memory/innodb/ut0link_buf             |      24.00006104 |
| memory/innodb/lock0lock               |      21.40064240 |
| memory/mysqld_openssl/openssl_malloc  |       9.51009655 |
| memory/innodb/read0read               |       8.19496155 |
| memory/mysys/KEY_CACHE                |       8.00215149 |
| memory/innodb/sync0arr                |       7.03147125 |
| memory/innodb/ha_innodb               |       6.87006950 |
| memory/innodb/lock_sys                |       5.25009155 |
| memory/sql/log_sink_pfs               |       5.00003052 |
| memory/innodb/ut0pool                 |       4.00017548 |
| memory/sql/dd::objects                |       2.83031464 |
| memory/innodb/std                     |       2.72618866 |
| memory/innodb/os0file                 |       2.63054657 |
| memory/innodb/os0event                |       2.34302521 |
| memory/sql/TABLE_SHARE::mem_root      |       2.31734467 |
| memory/innodb/trx0trx                 |       2.22647858 |
| memory/temptable/physical_ram         |       1.00003052 |
| memory/sql/dd::String_type            |       0.94942093 |
| memory/innodb/btr0pcur                |       0.89743423 |
+---------------------------------------+------------------+</pre>
<p>&nbsp;</p>
<p><span style="font-weight: 400">Plus I used PMM to collect memory information&nbsp;</span></p>
<p><img decoding="async" loading="lazy" class="alignnone wp-image-49857 size-medium_large" src="https://www.percona.com/wp-content/uploads/2026/06/allocation_with_incidents_describe-768x395.jpg" alt="" width="768" height="395"></p>
<p><span style="font-weight: 400">To simulate the load I used the sysbench-tpcc (tpc-c derivate test) variant and run the tests simulating a load of 1024 threads against a cluster based on machine with 16 Core and 64Gb volumes ~3k IOPS, so not gigantic but not small.&nbsp;</span></p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">The finding was almost immediate:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">+---------------------------------------+------------------+
| EVENT_NAME                            | current_usage_mb |
+---------------------------------------+------------------+
| memory/innodb/buf_buf_pool            |   46398.92578125 |
| memory/group_rpl/certification_info   |    1431.67934418 | &lt;constantly increasing
| memory/group_rpl/GCS_XCom::xcom_cache |    1066.63542366 |
| memory/sql/Gtid_set::Interval_chunk   |      95.52413940 |
| memory/innodb/log_buffer_memory       |      64.00096130 |
| memory/sql/TABLE                      |      48.17613125 |
| memory/innodb/memory                  |      35.08897400 |
| memory/innodb/ut0link_buf             |      24.00006104 |
| memory/innodb/lock0lock               |      21.40064240 |
| memory/innodb/read0read               |      14.86782837 |
| memory/mysqld_openssl/openssl_malloc  |      12.05916119 |
| memory/mysys/KEY_CACHE                |       8.00215149 |
| memory/innodb/sync0arr                |       7.03147125 |
| memory/innodb/ha_innodb               |       6.84074974 |
| memory/innodb/lock_sys                |       5.25009155 |
| memory/sql/log_sink_pfs               |       5.00003052 |
| memory/innodb/ut0pool                 |       4.00017548 |
| memory/sql/dd::objects                |       2.82012177 |
| memory/innodb/std                     |       2.72515869 |
| memory/innodb/os0file                 |       2.63054657 |
| memory/innodb/os0event                |       2.35884857 |
| memory/innodb/trx0trx                 |       2.22647858 |
| memory/sql/TABLE_SHARE::mem_root      |       1.83777618 |
| memory/innodb/trx0undo                |       1.26304626 |
| memory/mysys/lf_node                  |       1.08828735 |
+---------------------------------------+------------------+</pre>
<p>&nbsp;</p>
<p><span style="font-weight: 400"><br>
</span><span style="font-weight: 400">Ok then &hellip; What is the certification info???</span></p>
<h2><span style="font-weight: 400">What is group_rpl/certification_info?</span><a class="anchor-link" id="what-is-group_rpl-certification_info"></a></h2>
<p><span style="font-weight: 400">In MySQL, </span><span style="font-weight: 400">memory/group_rpl/certification_info</span><span style="font-weight: 400"> is a Performance Schema memory instrument. It tracks the exact amount of RAM allocated to store the Certification Database (or Certification Info).</span></p>
<p><span style="font-weight: 400">In Group Replication, nodes do not lock rows across the network while a transaction is executing. Instead, transactions execute locally and optimistically. When it is time to commit, the transaction undergoes a </span><i><span style="font-weight: 400">Certification Process</span></i><span style="font-weight: 400"> to ensure no other concurrent transaction in the cluster has modified the exact same rows. The </span><span style="font-weight: 400">certification_info</span><span style="font-weight: 400"> buffer is the in-memory hash map that makes this conflict detection possible.</span></p>
<h3><span style="font-weight: 400">1. What is it used for?</span><a class="anchor-link" id="1-what-is-it-used-for"></a></h3>
<p><span style="font-weight: 400">The </span><span style="font-weight: 400">certification_info</span><span style="font-weight: 400"> structure acts as a tracking ledger for recently modified rows.</span></p>
<p><span style="font-weight: 400">Here is how it works under the hood:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The Key-Value Pair: It is fundamentally an in-memory dictionary. The </span><i><span style="font-weight: 400">key</span></i><span style="font-weight: 400"> is the hash of a modified row (extracted from the transaction&rsquo;s &ldquo;write set&rdquo;), and the </span><i><span style="font-weight: 400">value</span></i><span style="font-weight: 400"> is the Global Transaction Identifier (GTID) of the transaction that successfully modified it.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Conflict Detection: When a new transaction attempts to commit, it broadcasts its write set and the &ldquo;snapshot version&rdquo; of the database it saw when it started. The certifier cross-references the incoming transaction&rsquo;s write set against the </span><span style="font-weight: 400">certification_info</span><span style="font-weight: 400"> map.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The Decision: If the </span><span style="font-weight: 400">certification_info</span><span style="font-weight: 400"> shows that a row was modified by a newer GTID that the incoming transaction did not &ldquo;see&rdquo; when it started, a conflict is flagged, and the transaction is aborted. If no conflict exists, the transaction is certified, and the </span><span style="font-weight: 400">certification_info</span><span style="font-weight: 400"> map is updated with the new write set and GTID.</span></li>
</ul>
<p><span style="font-weight: 400">The primary does not hold onto this memory out of stubbornness; it does so because purging that data too early would destroy the cluster&rsquo;s consistency in the event of a failover.</span></p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">In Group Replication, garbage collection for the </span><span style="font-weight: 400">certification_info</span><span style="font-weight: 400"> buffer is not triggered just because a transaction commits on the primary. It is triggered by a concept called the Stable Set.&nbsp;</span></p>
<p><span style="font-weight: 400">Every node in the cluster periodically broadcasts a message to the rest of the group saying, </span><i><span style="font-weight: 400">&ldquo;Here are the GTIDs I have successfully applied to my disk.&rdquo;</span></i><span style="font-weight: 400"> The cluster then calculates a </span><i><span style="font-weight: 400">global low watermark</span></i><span style="font-weight: 400">. This watermark is the highest transaction GTID that </span><i><span style="font-weight: 400">every single member</span></i><span style="font-weight: 400"> of the group has successfully applied. Garbage collection is only allowed to purge write-sets from the certification database that fall </span><i><span style="font-weight: 400">below</span></i><span style="font-weight: 400"> this global watermark. </span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400">To note that this purge is a synchronous operation during which writes are forbidden.</span></p>
<h3><span style="font-weight: 400">2. How the Apply Queue Stalls the Watermark</span><a class="anchor-link" id="2-how-the-apply-queue-stalls-the-watermark"></a></h3>
<p><span style="font-weight: 400">When a secondary node starts lagging, its </span><i><span style="font-weight: 400">applier queue</span></i><span style="font-weight: 400"> grows. This means the secondary is receiving transactions from the network quickly, but its SQL thread is too slow to actually execute them and commit them to disk.</span></p>
<p><span style="font-weight: 400">Because the secondary hasn&rsquo;t applied these transactions, it cannot report those GTIDs back to the group as &ldquo;finished.&rdquo;</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The lagging secondary&rsquo;s local watermark stalls.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Therefore, the </span><i><span style="font-weight: 400">global low watermark</span></i><span style="font-weight: 400"> for the entire cluster stalls.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Because the global watermark hasn&rsquo;t moved forward, the </span><span style="font-weight: 400">garbage_collect</span><span style="font-weight: 400"> function on the primary (and all other nodes) says, </span><i><span style="font-weight: 400">&ldquo;I am not allowed to delete any write-sets yet.&rdquo;</span></i></li>
<li style="font-weight: 400"><span style="font-weight: 400">As the primary continues to process new writes, the </span><span style="font-weight: 400">certification_info</span><span style="font-weight: 400"> memory buffer grows continuously.</span></li>
</ul>
<h3><span style="font-weight: 400">3. Why the Primary Cannot Purge Early</span><a class="anchor-link" id="3-why-the-primary-cannot-purge-early"></a></h3>
<p><span style="font-weight: 400">we might wonder: </span><i><span style="font-weight: 400">If the transaction is already committed on the primary, why does the primary care if the secondary has applied it? Why not just drop the write-set from its own memory?</span></i></p>
<p><span style="font-weight: 400">The answer comes down to </span><i><span style="font-weight: 400">Failover Safety</span></i><span style="font-weight: 400"> and </span><i><span style="font-weight: 400">Distributed Conflict Detection</span></i><span style="font-weight: 400">. GR is a shared-nothing, decentralized architecture. Even if you are running in Single-Primary&nbsp; mode (keep this in mind will be important later), the underlying engine uses the exact same logic as Multi-Primary mode.&nbsp;</span></p>
<p><span style="font-weight: 400">Here is why the primary is forbidden from purging that data:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The Failover Scenario: Imagine our primary node crashes right now. The lagging secondary (which still has a massive apply queue) is immediately elected as the new primary.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The Conflict Risk: As the new primary, it starts accepting new writes from your application. However, it still has thousands of old transactions in its applier queue that it hasn&rsquo;t written to disk yet!</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The Necessity of the Buffer: When a new write comes in, the new primary </span><i><span style="font-weight: 400">must</span></i><span style="font-weight: 400"> check if that write conflicts with any of the pending transactions in its apply queue. It does this by checking the </span><span style="font-weight: 400">certification_info</span><span style="font-weight: 400"> map. If the old primary had purged the global certification data early, the new primary wouldn&rsquo;t have the write-sets for those pending transactions. It would blindly accept the new write, causing a massive data conflict and breaking the replication group entirely.</span></li>
</ul>
<p><span style="font-weight: 400">Fine Marco, then what is the effect of this?</span></p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">Well, drums roll &hellip;</span></p>
<p><span style="font-weight: 400">&hellip; When a secondary node is elected as the new primary during a failover, it does not immediately open the floodgates to new writes. </span><b>It keeps its </b><b><i>super_read_only</i></b><b> variable set to ON until it has completely drained its local apply queue of all transactions that were certified prior to the election.</b></p>
<p><span style="font-weight: 400">This is an intentional design choice to guarantee that the new primary&rsquo;s state is completely consistent with the old primary before it starts accepting new data.</span></p>
<p>&nbsp;</p>
<h3><span style="font-weight: 400">4. Immediate Write Rejections (No Built-in Queuing)</span><a class="anchor-link" id="4-immediate-write-rejections-no-built-in-queuing"></a></h3>
<p><span style="font-weight: 400">The most critical impact to understand is that the new primary does not queue or pause new incoming writes while it catches up. It outright rejects them.</span></p>
<p><span style="font-weight: 400">If our application or proxy routes a COMMIT, INSERT, UPDATE, or DELETE to the new primary while it is still processing the old queue, MySQL will immediately throw an error back to the client:</span></p>
<p><span style="font-weight: 400">ERROR 1290 (HY000): The MySQL server is running with the &ndash;super-read-only option so it cannot execute this statement</span></p>
<h3><span style="font-weight: 400">5. The &ldquo;Brownout&rdquo; Window (Write Outage)</span><a class="anchor-link" id="5-the-brownout-window-write-outage"></a></h3>
<p><span style="font-weight: 400">Because of this behavior, a failover in MySQL Group Replication does not instantly restore write availability. Our cluster experiences a &ldquo;brownout&rdquo;, a period where reads might succeed, but writes are entirely blocked.</span></p>
<p><span style="font-weight: 400">The duration of this write outage is directly proportional to the size of the apply queue.</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">If the secondary was fully caught up, write availability is restored in milliseconds.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">If the secondary was lagging by 50 minutes, your application will suffer a 50 minute write outage while the node applies the backlog.</span></li>
</ul>
<h3><span style="font-weight: 400">6. Impact on Proxies (e.g., MySQL Router or ProxySQL)</span><a class="anchor-link" id="6-impact-on-proxies-e-g-mysql-router-or-proxysql"></a></h3>
<p><span style="font-weight: 400">If we are using a proxy layer to route your database traffic, the apply queue dictates how the proxy behaves during the transition:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">MySQL Router: It continuously monitors the cluster topology and the super_read_only flag. Even though the node has technically been elected primary, Router will not open the read-write port to it until the apply queue drains and super_read_only flips to OFF. Depending on your application timeouts, client connections will either hang waiting for a writable connection or fail completely.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">ProxySQL: Similar to Router, if it is configured to check for the read_only state, it will temporarily quarantine the new primary from the write hostgroup.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">HAProxy (in Operator): Monitor both Primary state and read_only state, but it expose the Primary to writes causing the application to fail (bug we need to fix)&nbsp;&nbsp;</span></li>
</ul>
<h3><span style="font-weight: 400">7. Read Traffic and Stale Data</span><a class="anchor-link" id="7-read-traffic-and-stale-data"></a></h3>
<p><span style="font-weight: 400">During this catch-up phase, the node will accept incoming </span><span style="font-weight: 400">SELECT</span><span style="font-weight: 400"> queries (since it is still a valid database). However, because it is actively churning through the old primary&rsquo;s backlog, the data being read is temporarily stale.</span></p>
<p><span style="font-weight: 400">If your application reads a row that is sitting in the apply queue but hasn&rsquo;t been committed to disk yet, it will get the old version of that row.</span></p>
<h2><span style="font-weight: 400">Why Flow Control is Critical</span><a class="anchor-link" id="why-flow-control-is-critical"></a></h2>
<p><span style="font-weight: 400">Because a large apply queue turns a seamless failover into a severe, application-breaking write outage, Group Replication includes the Flow Control feature.</span></p>
<p><span style="font-weight: 400">Flow Control monitors the size of the apply queues across all secondaries. If a secondary starts lagging too far behind, Flow Control should actively throttle the write throughput on the </span><i><span style="font-weight: 400">current</span></i><span style="font-weight: 400"> primary to allow the lagging node to catch up. It is essentially a trade-off: we accept a slight performance hit during normal operations to guarantee that your database recovers almost instantly during a failover.</span></p>
<p><b>However, this is not what really happens</b><span style="font-weight: 400">.</span></p>
<h3><span style="font-weight: 400">1. It is Reactive, Not Proactive (The Polling Blind Spot)</span><a class="anchor-link" id="1-it-is-reactive-not-proactive-the-polling-blind-spot"></a></h3>
<p><span style="font-weight: 400">Flow control does not intercept and evaluate every single transaction in real-time. Instead, it relies on a periodic polling interval governed by </span><span style="font-weight: 400">group_replication_flow_control_period</span><span style="font-weight: 400"> (which defaults to 1 second).</span></p>
<p><span style="font-weight: 400">Once a second, the cluster checks the size of the apply queues and the certifier queues.</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The Vulnerability: If our application generates a massive spike of 50,000 writes in 500 milliseconds, the primary will happily accept and certify all of them. Flow control will not even notice the spike until the next 1 second polling interval hits. By the time it decides to apply a throttle, the damage is already done, and the secondary&rsquo;s queue is already overflowing.</span></li>
</ul>
<h3><span style="font-weight: 400">2. The PID Controller&rsquo;s &ldquo;Soft Brake&rdquo; Math</span><a class="anchor-link" id="2-the-pid-controllers-soft-brake-math"></a></h3>
<p><span style="font-weight: 400">When flow control does decide to throttle, it does not simply freeze the primary. It uses a PID (Proportional-Integral-Derivative) controller algorithm to calculate a &ldquo;write quota&rdquo; (the maximum number of transactions the primary is allowed to commit in the next second).</span></p>
<p><span style="font-weight: 400">The PID controller is deliberately tuned to be gentle. It wants to gracefully degrade performance rather than cause immediate application timeouts.</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">When the secondary&rsquo;s queue breaches the </span><span style="font-weight: 400">group_replication_flow_control_applier_threshold</span><span style="font-weight: 400"> (default 25,000 transactions), the PID controller reduces the primary&rsquo;s quota incrementally.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The Failure Point: If the primary&rsquo;s incoming write rate is astronomically higher than the secondary&rsquo;s disk IO capacity, this incremental &ldquo;step down&rdquo; in the quota is too slow. The primary is still allowed to write, say, 10,000 transactions per second, while the secondary is only applying 2,000. The queue continues to grow aggressively despite the throttle being &ldquo;active.&rdquo;</span></li>
</ul>
<h3><span style="font-weight: 400">3. The Concurrency Mismatch (Parallel vs. Serial)</span><a class="anchor-link" id="3-the-concurrency-mismatch-parallel-vs-serial"></a></h3>
<p><span style="font-weight: 400">This is often the silent killer that defeats flow control. Flow control makes mathematical assumptions about how fast the secondary </span><i><span style="font-weight: 400">should</span></i><span style="font-weight: 400"> be able to apply transactions based on recent history.</span></p>
<p><span style="font-weight: 400">However, the primary node might be executing writes using hundreds of highly concurrent threads. The secondary relies on the parallel applier to keep up. If the incoming workload suddenly includes transactions that cannot be parallelized, such as writes hitting overlapping rows, cascading foreign key updates, or DDL statements, the secondary&rsquo;s applier instantly drops from executing in parallel down to a single, serialized thread.</span></p>
<p><span style="font-weight: 400">When this serialization happens, the secondary&rsquo;s applier rate plummets instantly. Flow control, which only checks in once a second and adjusts gradually, cannot brake the primary fast enough to compensate for the secondary suddenly dropping to a crawl.</span></p>
<h2><span style="font-weight: 400">What can we do?</span><a class="anchor-link" id="what-can-we-do"></a></h2>
<p><span style="font-weight: 400">At the moment of writing there are only two things that can be done.</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Make Flow control more aggressive</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Increase the number of replication appliers</span></li>
</ol>
<p>&nbsp;</p>
<h3><span style="font-weight: 400">1. Making Flow Control More Aggressive</span><a class="anchor-link" id="1-making-flow-control-more-aggressive"></a></h3>
<p><span style="font-weight: 400">We can configure Flow Control to be a bit more aggressive. It will still remain a </span><i><span style="font-weight: 400">suggestion</span></i><span style="font-weight: 400"> but a strong one.</span></p>
<p><span style="font-weight: 400">How it works (The Configuration):</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Lower the Threshold: By reducing </span><span style="font-weight: 400">group_replication_flow_control_applier_threshold</span><span style="font-weight: 400"> (default is 25,000) to something like 1,000 or 500, we force the PID controller to kick in almost immediately when a spike occurs.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Remove the Safety Net: By keeping&nbsp; </span><span style="font-weight: 400">group_replication_flow_control_min_quota</span><span style="font-weight: 400"> to </span><span style="font-weight: 400">0 </span><span style="font-weight: 400">(default), we remove the minimum write guarantee. If the secondary falls behind, Flow Control is allowed to throttle the primary&rsquo;s writes down to zero, also if this will never happen.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Increase the Sensitivity: We can tweak the PID controller&rsquo;s math (using the derivative and proportional tuning variables) to react much more aggressively to queue growth.</span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400"> &nbsp; &nbsp; &nbsp; group_replication_flow_control_hold_percent=100</span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400"> &nbsp; &nbsp; &nbsp; group_replication_flow_control_release_percent=5</span></li>
</ul>
<p>&nbsp;</p>
<p><b>The reality check, does it work?:</b></p>
<p><span style="font-weight: 400">If the expectation is to have a rigid control over the applier queue on the lagging secondary, then the answer is </span><b>NO</b><span style="font-weight: 400">. No matter what, at the moment flow control is not designed to act as we are used to in PXC (Percona Xtradb Cluster), where we have a rigid control of the pending queue also at the cost of delaying the writes. In Group Replication&nbsp; the Flow Control will never bring the write to 0, the unfortunate aspect is that the mechanism is not enough to keep the queue under control.</span></p>
<p>&nbsp;</p>
<h3><span style="font-weight: 400">2. Increasing Replication Appliers&nbsp;</span><a class="anchor-link" id="2-increasing-replication-appliers"></a></h3>
<p><span style="font-weight: 400">To help the secondary chew through the queue faster, we can increase the number of parallel threads it uses to write to disk.</span></p>
<p><b>How it works</b><span style="font-weight: 400">: We can increase the </span><span style="font-weight: 400">replica_parallel_workers</span><span style="font-weight: 400"> (formerly </span><span style="font-weight: 400">slave_parallel_workers</span><span style="font-weight: 400">) setting. GR is exceptionally smart about this. Because of the certification process we discussed earlier, GR already knows exactly which transactions modify which rows. It uses a writeset-based dependency tracker to safely hand off non-conflicting transactions to multiple worker threads simultaneously.</span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400">The formula that is normally used to calculate the number of replication workers is to set 2.5 workers for each available core. IE if we have 14000m CPUs in our CR (K8) then we can assign ~35 workers, this is definitely higher than the default value of 4.&nbsp;&nbsp;&nbsp;</span></p>
<p><b>The reality check, does it work?</b><span style="font-weight: 400">:&nbsp; </span><b>Yes</b><span style="font-weight: 400">, but only if our workload allows it.</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The Catch &ndash; The Serialization Wall: Parallel appliers only work if the transactions do not conflict. If our application has 50 concurrent threads all trying to update the same &ldquo;inventory count&rdquo; row, or updating a highly contentious table, those transactions </span><i><span style="font-weight: 400">cannot</span></i><span style="font-weight: 400"> be parallelized. The secondary&rsquo;s coordinator thread will see the row-level conflicts and force those transactions to wait in line and execute sequentially. We could allocate 128 parallel workers, but 127 of them will sit idle while one thread does all the work.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The Catch &ndash; Context Switching: More threads do not magically create more disk IOPS. If we set the workers too high (e.g., beyond the physical CPU core count or disk IO capacity), the secondary&rsquo;s InnoDB engine will spend more time context-switching and fighting over internal mutex locks than actually committing data. In many cases, over-allocating parallel workers actually </span><i><span style="font-weight: 400">slows down</span></i><span style="font-weight: 400"> the apply rate.</span></li>
</ul>
<h2><span style="font-weight: 400">Do we have any conclusions?</span><a class="anchor-link" id="do-we-have-any-conclusions"></a></h2>
<h3><span style="font-weight: 400">1. If HA is the goal, enforce Strict Flow Control</span><a class="anchor-link" id="1-if-ha-is-the-goal-enforce-strict-flow-control"></a></h3>
<p><span style="font-weight: 400">If our absolute top priority is High Availability, specifically achieving a near-zero Recovery Time Objective (RTO), we must configure an aggressive flow control.</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The Logic: Fast failovers require small apply queues. To guarantee a small apply queue, we must strictly throttle the primary the millisecond the secondary starts to lag.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The Trade-off: we are protecting the cluster&rsquo;s failover readiness at the expense of application write latency. If there is a massive write spike, our application will face timeouts and connection errors, but if the primary server suddenly catches fire, our database will recover and elect a new primary almost instantly.</span></li>
</ul>
<p><span style="font-weight: 400">The problem is that Group Replication is not able to act like that today, this is something we eventually need to implement to have better HA.</span></p>
<h3><span style="font-weight: 400">2. If Performance is the goal, relax Flow Control</span><a class="anchor-link" id="2-if-performance-is-the-goal-relax-flow-control"></a></h3>
<p><span style="font-weight: 400">If our top priority is keeping the application fast and ensuring </span><span style="font-weight: 400">COMMIT</span><span style="font-weight: 400"> latencies remain extremely low, we should relax flow control or rely on the generous defaults.</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The Logic: By relaxing flow control, we allow the primary to run at the absolute maximum speed its local disks and CPU allow. It does not care if the secondaries fall behind. Our application users remain happy and experience zero throttling.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The Trade-off: We are accepting severe risks to your HA posture. If the primary crashes while the secondaries have a massive apply queue, we will suffer a long write outage (the brownout) while the new primary catches up. Additionally, we are accepting the risk that the </span><span style="font-weight: 400">certification_info</span><span style="font-weight: 400"> memory buffer will grow significantly on the primary and eventually have the pod OOMKilled .</span></li>
</ul>
<h3><span style="font-weight: 400">3. Is this not what Asynchronous replication with semy-sync offers?</span><a class="anchor-link" id="3-is-this-not-what-asynchronous-replication-with-semy-sync-offers"></a></h3>
<p>&nbsp;</p>
<h4><i><span style="font-weight: 400">1. The Similarities</span></i></h4>
<p><span style="font-weight: 400">If we look purely at how a single transaction flows and how a failover behaves, GR and Semi-Sync look like twins:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The Durability Guarantee: </span><i><span style="font-weight: 400">Semi-Sync:</span></i><span style="font-weight: 400"> The primary waits to commit until at least one secondary confirms it has received the transaction and written it to its local Relay Log.&nbsp;</span>
<ul>
<li style="font-weight: 400"><i><span style="font-weight: 400">GR:</span></i><span style="font-weight: 400"> The primary waits to commit until a majority quorum of nodes confirm they have received the transaction, certified it, and written it to their local relay logs.</span></li>
</ul>
</li>
<li style="font-weight: 400"><span style="font-weight: 400">The Failover Delay (The Queue):&nbsp; In both systems, the secondary receiving the data does not mean the secondary has applied the data to its InnoDB tables.</span>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">If a crash happens, both systems require the new primary to completely execute its pending queue (Relay Log for Semi-Sync, Apply Queue for GR) before it is safe to accept new writes.</span></li>
</ul>
</li>
</ul>
<h4><i><span style="font-weight: 400">2. The Crucial Differences</span></i></h4>
<p><span style="font-weight: 400">If they behave so similarly, why use GR at all? </span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400">The differences lie entirely in automation, consensus, and split-brain protection. Semi-Sync is just a data transport mechanism; GR is a full state-machine cluster.</span></p>
<p><span style="font-weight: 400">Here is what GR gives you that Semi-Sync does not:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Automatic Election and Orchestration:</span>
<ul>
<li style="font-weight: 400"><i><span style="font-weight: 400">Semi-Sync:</span></i><span style="font-weight: 400"> If the primary dies, Semi-Sync does nothing. The cluster sits there broken. You must rely on external tools (like Orchestrator or manual DBA intervention) to detect the crash, pick the most up-to-date secondary, wait for its relay log to apply, disable </span><span style="font-weight: 400">read_only</span><span style="font-weight: 400">, and re-point the application.</span></li>
<li style="font-weight: 400"><i><span style="font-weight: 400">GR:</span></i><span style="font-weight: 400"> The cluster detects the failure natively. The remaining nodes use Paxos consensus to elect a new primary automatically, manage the queue drain natively via the </span><span style="font-weight: 400">super_read_only</span><span style="font-weight: 400"> flip we discussed, and self-heal.</span></li>
</ul>
</li>
<li style="font-weight: 400"><span style="font-weight: 400">Split-Brain Protection (Network Partitions):</span>
<ul>
<li style="font-weight: 400"><i><span style="font-weight: 400">Semi-Sync:</span></i><span style="font-weight: 400"> If our network splits in half, an external failover tool might accidentally promote a secondary while the old primary is still alive and accepting writes. We now have a split-brain, and our data is permanently corrupted.</span></li>
<li style="font-weight: 400"><i><span style="font-weight: 400">GR:</span></i><span style="font-weight: 400"> GR enforces strict quorum. If a network split happens, the side of the network with the minority of nodes will automatically fence itself off and refuse all writes. Split-brain is mathematically prevented.</span></li>
</ul>
</li>
<li style="font-weight: 400"><span style="font-weight: 400">The Certification Database:</span>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">As we established, GR requires the certification map to ensure the new primary doesn&rsquo;t accept writes that conflict with its unapplied queue. Semi-Sync does not have this; it relies entirely on the external failover tool to guarantee no writes touch the new primary until the relay log is 100% applied.</span></li>
</ul>
</li>
</ul>
<h4><i><span style="font-weight: 400">3. Final observation</span></i></h4>
<p><span style="font-weight: 400">If we are using Single-Primary GR with relaxed flow control, we have essentially built a highly-automated, consensus-driven version of Semi-Sync replication.&nbsp;</span></p>
<p><span style="font-weight: 400">We have the exact same apply-queue bottleneck during failover, but we have traded the need for external orchestrator tools for built-in Paxos consensus and native split-brain protection.</span></p>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">Conclusions (for real)</span><a class="anchor-link" id="conclusions-for-real"></a></h2>
<p><span style="font-weight: 400">When we run MySQL on a traditional, dedicated Virtual Machine, memory limits are &ldquo;soft.&rdquo; If the </span><span style="font-weight: 400">certification_info</span><span style="font-weight: 400"> database explodes and consumes an extra 10GB of RAM because of the applier lag, the Linux OS might start aggressively swapping inactive pages to disk, but the MySQL process usually survives. Performance degrades, but the database stays online.</span></p>
<p><span style="font-weight: 400">In Kubernetes, memory limits are &ldquo;hard.&rdquo; As we discussed earlier, Kubernetes enforces pod memory limits via cgroups v2 (</span><span style="font-weight: 400">memory.max</span><span style="font-weight: 400">). The Linux kernel&rsquo;s OOM Killer has no understanding of database quorum, failover states, or apply queues. It only sees math: </span><i><span style="font-weight: 400">Working Set Size &gt; </span></i><i><span style="font-weight: 400">memory.max</span></i><i><span style="font-weight: 400"> = Terminate Process (Exit Code 137).</span></i></p>
<h3><span style="font-weight: 400">The Chain Reaction of Relaxed Flow Control in k8s</span><a class="anchor-link" id="the-chain-reaction-of-relaxed-flow-control-in-k8s"></a></h3>
<p><span style="font-weight: 400">If we prioritize &ldquo;performance&rdquo; by relaxing Flow Control in a Kubernetes environment, we are essentially setting a ticking time bomb. Here is the chain of events:</span></p>
<ol>
<li style="font-weight: 400"><b>The Spike</b><span style="font-weight: 400">: Our application experiences a massive write spike.</span></li>
<li style="font-weight: 400"><b>The Queue</b><span style="font-weight: 400">: The secondary pod&rsquo;s disk cannot keep up, and its applier queue grows to 1,000,000 transactions.</span></li>
<li style="font-weight: 400"><b>The Memory Sprawl</b><span style="font-weight: 400">: Because the queue is large, the global low-watermark stalls. The Primary pod is forbidden from garbage collecting the </span><span style="font-weight: 400">certification_info</span><span style="font-weight: 400"> map. The in-memory hash map balloons in size.</span></li>
<li style="font-weight: 400"><b>The Execution</b><span style="font-weight: 400">: The </span><i><span style="font-weight: 400">memory.current</span></i><span style="font-weight: 400"> metric will reach the </span><i><span style="font-weight: 400">memory.max</span></i><span style="font-weight: 400">, kernel will trigger the OMMKill process. First action will be to try to free the page.cache related to the process. If the purge is successful and the memory.current is less than </span><i><span style="font-weight: 400">memory.max</span></i><span style="font-weight: 400"> then the process will persist, otherwise the kernel will kill it. </span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400">We can use the WSS metric to predict a successful OMMKill.</span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400"> The Primary pod&rsquo;s Working Set Size (WSS) breaches its Kubernetes memory limit, this is a fair estimate not an absolute value.</span></li>
<li style="font-weight: 400"><b>The Catastrophe</b><span style="font-weight: 400">: The Linux OOM Killer instantly assassinates the Primary MySQL process.</span></li>
</ol>
<p><span style="font-weight: 400">Because we tried to avoid a few seconds of write latency by keeping relaxed Flow Control, we inadvertently caused a hard crash of the primary database pod, with long write downtime.</span></p>
<h3><span style="font-weight: 400">The Architectural Law</span><a class="anchor-link" id="the-architectural-law"></a></h3>
<p><span style="font-weight: 400">Therefore, here is my statement as architectural law for containerized environments: </span><b>In Kubernetes, High Availability and Pod stability are so intrinsically linked that Flow Control </b><b><i>must</i></b><b> act as hard as it can to cap the apply queue.</b></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">We cannot allow unbounded memory growth in a container. The only way to bound </span><span style="font-weight: 400">certification_info</span><span style="font-weight: 400"> memory is to bound the apply queue.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The only way to bound the apply queue is with strict, aggressive Flow Control.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Increasing the number of replication appliers helps but is not the conclusive answer.</span></li>
</ul>
<p><span style="font-weight: 400">In a Kubernetes environment, we must tune </span><span style="font-weight: 400">group_replication_flow_control_applier_threshold</span><span style="font-weight: 400"> to a strict, low number, and accept that during massive traffic spikes, our application </span><i><span style="font-weight: 400">will</span></i><span style="font-weight: 400"> experience write throttling. It is infinitely better for our application&rsquo;s connection pool to wait 2 seconds for a </span><span style="font-weight: 400">COMMIT</span><span style="font-weight: 400"> to succeed than for the primary database pod to be violently OOMKilled by the kernel, and have to wait for minutes or hours to recover write capabilities.</span></p>
<h3><span style="font-weight: 400">Note</span><a class="anchor-link" id="note"></a></h3>
<p><span style="font-weight: 400">Just as a mention this is exactly how Percona Operator with Percona Xtradb Cluster works. To be more specific, PXC and in general solutions based on Galera have a Flow Control mechanism that enforces the queue to be inside hard limits. While this more invasive control may be noticeable at application level, it guarantees that the other nodes are not lagging behind the primary and this is why it is a stronger HA solution in the Kubernetes environment.</span></p>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">Reference</span><a class="anchor-link" id="reference"></a></h2>
<p><a href="https://github.com/Tusamarco/mysqloperatorcalculator"><span style="font-weight: 400">https://github.com/Tusamarco/mysqloperatorcalculator</span></a></p>
<p><span style="font-weight: 400">Managing Resources and OOMKills: </span><a href="https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"><span style="font-weight: 400">Resource Management for Pods and Containers</span></a> <i><span style="font-weight: 400">(This page details how memory limits are enforced reactively by the Linux kernel via OOM kills).</span></i></p>
<p><span style="font-weight: 400">How WSS triggers Evictions: </span><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/"><span style="font-weight: 400">Node-pressure Eviction</span></a> <i><span style="font-weight: 400">(This page explicitly details how the </span></i><i><span style="font-weight: 400">kubelet</span></i><i><span style="font-weight: 400"> uses the </span></i><i><span style="font-weight: 400">memory.available</span></i><i><span style="font-weight: 400"> signal, which is derived from node capacity minus the working set size).</span></i></p>
<p><span style="font-weight: 400">Latest changes. </span><a href="https://github.com/google/cadvisor/blob/195858077459e69455fd9621fcbaeaf377d69d0e/container/libcontainer/handler.go#L865"><span style="font-weight: 400">Pointer to the code</span></a><span style="font-weight: 400">&nbsp;</span></p>
<p><span style="font-weight: 400">Swap Memory Management (Core Concepts &amp; Configuration): </span><a href="https://kubernetes.io/docs/concepts/cluster-administration/swap-memory-management/"><span style="font-weight: 400">https://kubernetes.io/docs/concepts/cluster-administration/swap-memory-management/</span></a></p>
<p>The post <a href="https://www.percona.com/blog/the-failover-brownout-rethinking-high-availability-in-mysql-group-replication/">The Failover Brownout: Rethinking High Availability in MySQL Group Replication</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/the-failover-brownout-rethinking-high-availability-in-mysql-group-replication/">The Failover Brownout: Rethinking High Availability in MySQL Group Replication</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Foundation Sea Lion Champions Nominees: Sylvain Arbaudie</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-sylvain-arbaudie/" />
      <id>https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-sylvain-arbaudie/</id>
      <updated>2026-06-15T06:30:16+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>Interview with Sylvain Arbaudie, nominated in the Technical Excellence category.<br />
The MariaDB Foundation Sea Lion Champions program celebrates the people and organizations who help make the MariaDB ecosystem stronger, more open, and more useful for everyone. …<br />
Continue reading \"MariaDB Foundation Sea Lion Champions Nominees: Sylvain Arbaudie\"<br />
The post MariaDB Foundation Sea Lion Champions Nominees: Sylvain Arbaudie appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-sylvain-arbaudie/">MariaDB Foundation Sea Lion Champions Nominees: Sylvain Arbaudie</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Interview with Sylvain Arbaudie, nominated in the Technical Excellence category.<br>
The MariaDB Foundation Sea Lion Champions program celebrates the people and organizations who help make the MariaDB ecosystem stronger, more open, and more useful for everyone. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-sylvain-arbaudie/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Foundation Sea Lion Champions Nominees: Sylvain Arbaudie&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-sylvain-arbaudie/">MariaDB Foundation Sea Lion Champions Nominees: Sylvain Arbaudie</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-sylvain-arbaudie/">MariaDB Foundation Sea Lion Champions Nominees: Sylvain Arbaudie</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB + DuckDB: A New Playground for Analytics – A First Look at the New Storage Engine</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-duckdb-a-new-playground-for-analytics-a-first-look-at-the-new-storage-engine/" />
      <id>https://mariadb.org/mariadb-duckdb-a-new-playground-for-analytics-a-first-look-at-the-new-storage-engine/</id>
      <updated>2026-06-12T11:53:16+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>MariaDB just announced it has learned to quack: the new DuckDB storage engine has joined the large family of storage engines in MariaDB Server. …<br />
Continue reading \"MariaDB + DuckDB: A New Playground for Analytics – A First Look at the New Storage Engine\"<br />
The post MariaDB + DuckDB: A New Playground for Analytics – A First Look at the New Storage Engine appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-duckdb-a-new-playground-for-analytics-a-first-look-at-the-new-storage-engine/">MariaDB + DuckDB: A New Playground for Analytics – A First Look at the New Storage Engine</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB just <a href="https://mariadb.org/duckdb-storage-engine-for-mariadb-when-the-sea-lion-learns-to-quack/">announced </a>it has learned to quack: the new DuckDB storage engine has joined the large family of storage engines in MariaDB Server. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-duckdb-a-new-playground-for-analytics-a-first-look-at-the-new-storage-engine/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB + DuckDB: A New Playground for Analytics &ndash; A First Look at the New Storage Engine&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-duckdb-a-new-playground-for-analytics-a-first-look-at-the-new-storage-engine/">MariaDB + DuckDB: A New Playground for Analytics &ndash; A First Look at the New Storage Engine</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-duckdb-a-new-playground-for-analytics-a-first-look-at-the-new-storage-engine/">MariaDB + DuckDB: A New Playground for Analytics – A First Look at the New Storage Engine</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Guide Multi-Cluster MongoDB on GKE with MCS, Percona Operator</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/06/12/multi-cluster-mongodb-percona-operator/" />
      <id>https://percona.community/blog/2026/06/12/multi-cluster-mongodb-percona-operator/</id>
      <updated>2026-06-12T11:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>Multi-Cluster MongoDB on GKE with MCS Guide Deploying the Percona Operator for MongoDB across two GKE clusters using Multi-Cluster Services (MCS)</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/06/12/multi-cluster-mongodb-percona-operator/">Guide Multi-Cluster MongoDB on GKE with MCS, Percona Operator</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<h1 id="multi-cluster-mongodb-on-gke-with-mcs-guide">Multi-Cluster MongoDB on GKE with MCS Guide<a class="anchor-link" id="multi-cluster-mongodb-on-gke-with-mcs-guide"></a></h1>
<p>Deploying the Percona Operator for MongoDB across two GKE clusters using Multi-Cluster Services (MCS)</p>
<p>This guide walks through deploying a highly available MongoDB replica set that spans two GKE clusters using the <a href="https://github.com/percona/percona-server-mongodb-operator" target="_blank" rel="noopener noreferrer">Percona Operator for MongoDB</a> and <a href="https://cloud.google.com/kubernetes-engine/docs/concepts/multi-cluster-services" target="_blank" rel="noopener noreferrer">GKE Multi-Cluster Services (MCS)</a>.</p>
<h2 id="architecture-overview">Architecture Overview<a class="anchor-link" id="architecture-overview"></a></h2>
<p>Both clusters belong to the same <strong>GKE Fleet</strong>. MCS gives each cluster DNS names for the<br>
other cluster&rsquo;s services (<code>*.psmdb.svc.clusterset.local</code>). <strong><code>externalNodes</code></strong> in the<br>
Percona CR tells MongoDB to use those names as replica-set members. MCS provides<br>
cross-cluster DNS; <code>externalNodes</code> wires MongoDB to use it.</p>
<h3 id="what-runs-on-each-cluster">What runs on each cluster<a class="anchor-link" id="what-runs-on-each-cluster"></a></h3>
<p>Each site runs a <strong>sharded</strong> MongoDB cluster (not a single 6-node replset):</p>
<pre class="mermaid">
flowchart TB
subgraph Main["Main cluster, Operator MANAGED"]
direction TB
MO["mongos &times;3"]
MC["cfg replset: cfg-0, cfg-1, cfg-2"]
MR["shard rs0: rs0-0, rs0-1, rs0-2"]
MO --&gt; MC
MO --&gt; MR
end
subgraph Replica["Replica cluster, Operator UNMANAGED"]
direction TB
RO["mongos &times;3"]
RC["cfg replset: cfg-0, cfg-1, cfg-2"]
RR["shard rs0: rs0-0, rs0-1, rs0-2"]
RO --&gt; RC
RO --&gt; RR
end
MC |"6 members, config servers"| RC
MR |"6 members, shard data"| RR
</pre>
<p>Once interconnected, each replset has <strong>6 members</strong> (3 on main + 3 on replica). One<br>
PRIMARY per replset; the rest are SECONDARY.</p>
<h3 id="mcs-is-bidirectional">MCS is bidirectional<a class="anchor-link" id="mcs-is-bidirectional"></a></h3>
<p>Both clusters <strong>export</strong> their own services and <strong>import</strong> the other cluster&rsquo;s services:</p>
<pre class="mermaid">
flowchart LR
subgraph Main["Main cluster"]
ExpM["ServiceExportn(main services)"]
ImpM["ServiceImportn(replica services)"]
end
subgraph Replica["Replica cluster"]
ExpR["ServiceExportn(replica services)"]
ImpR["ServiceImportn(main services)"]
end
ExpM --&gt;|"MCS Fleet"| ImpR
ExpR --&gt;|"MCS Fleet"| ImpM
ImpM --&gt; DNS["*.psmdb.svc.clusterset.local"]
ImpR --&gt; DNS
</pre>
<p>Each cluster sees <strong>18 ServiceImports</strong>, 9 from main + 9 from replica.</p>
<h2 id="prerequisites">Prerequisites<a class="anchor-link" id="prerequisites"></a></h2>
<ul>
<li><code>gcloud</code> CLI installed and authenticated</li>
<li><code>kubectl</code> installed</li>
<li><code>yq</code> installed (<code>brew install yq</code> on macOS or <code>apt install yq</code> on Linux)</li>
<li>A GCP project with billing enabled</li>
<li>Owner or Editor role on the project</li>
</ul>
<p>If you want to see all the command in a Readmefile, see the Github repository <a href="https://github.com/edithturn/psmdb-operator-multicluster-demo/blob/main/README.md" target="_blank" rel="noopener noreferrer">here</a>.</p>
<h2 id="file-layout">File Layout<a class="anchor-link" id="file-layout"></a></h2>
<p>After completing this guide you will have:</p>
<p><strong>Kubeconfigs</strong> (in <code>~/.kube/psmdb-demo/</code>, outside this repo):</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-2">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">~/.kube/psmdb-demo/gcp-main_config # kubeconfig for main cluster
</span></span><span class="line"><span class="cl">~/.kube/psmdb-demo/gcp-replica_config # kubeconfig for replica cluster</span></span></code></pre>
</div>
</div>
</div>
<p><strong>Manifests and exports</strong> (in this working directory):</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-3">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">cr-main.yaml # Main cluster initial config
</span></span><span class="line"><span class="cl">cr-main-after.yaml # Main cluster config with externalNodes
</span></span><span class="line"><span class="cl">cr-replica.yaml # Replica cluster config
</span></span><span class="line"><span class="cl">cr-replica-after.yaml # Replica cluster config with externalNodes</span></span></code></pre>
</div>
</div>
</div>
<p>The following files are local only, created during the guide, listed in <code>.gitignore</code>, <strong>do not commit</strong> (contain passwords, TLS keys, and encryption keys):</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-4">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">my-cluster-secrets.yml # exported from main (do not apply directly)
</span></span><span class="line"><span class="cl">main-cluster-ssl.yml # exported from main (do not apply directly)
</span></span><span class="line"><span class="cl">main-cluster-ssl-internal.yml # exported from main (do not apply directly)
</span></span><span class="line"><span class="cl">my-cluster-name-mongodb-encryption-key.yml # exported from main (do not apply directly)
</span></span><span class="line"><span class="cl">my-cluster-secrets-replica.yaml # modified for replica, apply this
</span></span><span class="line"><span class="cl">replica-cluster-ssl.yml # modified for replica, apply this
</span></span><span class="line"><span class="cl">replica-cluster-ssl-internal.yml # modified for replica, apply this
</span></span><span class="line"><span class="cl">my-cluster-name-mongodb-encryption-key-replica.yml # modified for replica, apply this</span></span></code></pre>
</div>
</div>
</div>
<blockquote>
<p><strong>Why two versions of cr-main.yaml?</strong><br>
The initial <code>cr-main.yaml</code> deploys the cluster without knowing the replica node addresses.<br>
After the replica cluster is running and ServiceImports are confirmed, <code>cr-main-after.yaml</code><br>
adds <code>externalNodes</code> to interconnect the two clusters. This avoids DNS failures during<br>
initial deployment.</p>
</blockquote>
<h2 id="step-1-set-your-project-id">Step 1: Set your project ID<a class="anchor-link" id="step-1-set-your-project-id"></a></h2>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-5">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PROJECT_ID</span><span class="o">=</span>your_project_id</span></span></code></pre>
</div>
</div>
</div>
<p>Verify:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-6">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$PROJECT_ID</span></span></span></code></pre>
</div>
</div>
</div>
<h2 id="step-2-enable-required-gcp-apis">Step 2: Enable required GCP APIs<a class="anchor-link" id="step-2-enable-required-gcp-apis"></a></h2>
<p>These APIs are required for MCS, Fleet, and Workload Identity to work.</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-7">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcloud services <span class="nb">enable</span> <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> multiclusterservicediscovery.googleapis.com <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> gkehub.googleapis.com <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> cloudresourcemanager.googleapis.com <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> trafficdirector.googleapis.com <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> dns.googleapis.com <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --project <span class="nv">$PROJECT_ID</span></span></span></code></pre>
</div>
</div>
</div>
<p>Expected output: each API shows <code>Enabling API...</code> then <code>Operation finished successfully</code>.</p>
<h2 id="step-3-create-two-gke-clusters">Step 3: Create two GKE clusters<a class="anchor-link" id="step-3-create-two-gke-clusters"></a></h2>
<p>Both clusters must be created with <code>--workload-metadata=GKE_METADATA</code> and <code>--workload-pool</code><br>
to enable Workload Identity Federation, which is required by the MCS importer.</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-8">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Main cluster</span>
</span></span><span class="line"><span class="cl">gcloud container clusters create main-cluster <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --zone us-central1-a <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --machine-type n1-standard-4 <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --num-nodes<span class="o">=</span><span class="m">3</span> <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --workload-metadata<span class="o">=</span>GKE_METADATA <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --workload-pool<span class="o">=</span><span class="nv">$PROJECT_ID</span>.svc.id.goog
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Replica cluster</span>
</span></span><span class="line"><span class="cl">gcloud container clusters create replica-cluster <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --zone us-central1-a <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --machine-type n1-standard-4 <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --num-nodes<span class="o">=</span><span class="m">3</span> <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --workload-metadata<span class="o">=</span>GKE_METADATA <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --workload-pool<span class="o">=</span><span class="nv">$PROJECT_ID</span>.svc.id.goog</span></span></code></pre>
</div>
</div>
</div>
<blockquote>
<p>Both clusters use <code>us-central1-a</code> here for simplicity. In a production setup,<br>
use different zones or regions (e.g. <code>us-east1-b</code>) for the replica to achieve<br>
true regional isolation.</p>
</blockquote>
<h2 id="step-4-enable-mcs-and-register-clusters-to-the-fleet">Step 4: Enable MCS and register clusters to the Fleet<a class="anchor-link" id="step-4-enable-mcs-and-register-clusters-to-the-fleet"></a></h2>
<p>GKE uses a Fleet to group clusters. There is exactly one Fleet per GCP project,<br>
automatically named after the project ID. MCS works across all clusters in the same Fleet.</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-9">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Enable MCS at the Fleet level</span>
</span></span><span class="line"><span class="cl">gcloud container fleet multi-cluster-services <span class="nb">enable</span> --project <span class="nv">$PROJECT_ID</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Register main cluster to the Fleet</span>
</span></span><span class="line"><span class="cl">gcloud container fleet memberships register main-cluster <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --gke-cluster us-central1-a/main-cluster <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --enable-workload-identity
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Register replica cluster to the Fleet</span>
</span></span><span class="line"><span class="cl">gcloud container fleet memberships register replica-cluster <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --gke-cluster us-central1-a/replica-cluster <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --enable-workload-identity</span></span></code></pre>
</div>
</div>
</div>
<h2 id="step-5-grant-iam-permissions-to-the-mcs-importer">Step 5: Grant IAM permissions to the MCS Importer<a class="anchor-link" id="step-5-grant-iam-permissions-to-the-mcs-importer"></a></h2>
<p>The MCS Importer is a GKE-managed pod in the <code>gke-mcs</code> namespace on each cluster.<br>
Its job is to watch for <code>ServiceExport</code> resources and create <code>ServiceImport</code> objects<br>
on other clusters. It needs read access to your VPC network configuration to do this.</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-10">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Get the numeric project number (different from the project ID string)</span>
</span></span><span class="line"><span class="cl"><span class="nv">PROJECT_NUMBER</span><span class="o">=</span><span class="k">$(</span>gcloud projects describe <span class="nv">$PROJECT_ID</span> <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --format<span class="o">=</span><span class="s2">"value(projectNumber)"</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Grant compute.networkViewer to the MCS importer service account</span>
</span></span><span class="line"><span class="cl">gcloud projects add-iam-policy-binding <span class="nv">$PROJECT_ID</span> <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --member <span class="s2">"principal://iam.googleapis.com/projects/</span><span class="nv">$PROJECT_NUMBER</span><span class="s2">/locations/global/workloadIdentityPools/</span><span class="nv">$PROJECT_ID</span><span class="s2">.svc.id.goog/subject/ns/gke-mcs/sa/gke-mcs-importer"</span> <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --role <span class="s2">"roles/compute.networkViewer"</span></span></span></code></pre>
</div>
</div>
</div>
<h2 id="step-6-verify-mcs-is-active-on-both-clusters">Step 6: Verify MCS is active on both clusters<a class="anchor-link" id="step-6-verify-mcs-is-active-on-both-clusters"></a></h2>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-11">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcloud container fleet multi-cluster-services describe --project <span class="nv">$PROJECT_ID</span></span></span></code></pre>
</div>
</div>
</div>
<p>Expected output, both clusters must show <code>code: OK</code>:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">yaml</span><button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-12">
<div class="highlight">
<pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">membershipStates</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">projects/XXXXXXX/locations/us-central1/memberships/main-cluster</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">code</span><span class="p">:</span><span class="w"> </span><span class="l">OK</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Firewall successfully updated</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">projects/XXXXXXX/locations/us-central1/memberships/replica-cluster</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">code</span><span class="p">:</span><span class="w"> </span><span class="l">OK</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Firewall successfully updated</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">resourceState</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l">ACTIVE</span></span></span></code></pre>
</div>
</div>
</div>
<blockquote>
<p>If you see <code>code: PENDING</code> wait 2&ndash;3 minutes and re-run. If you see errors,<br>
check that both clusters were created with <code>--workload-pool</code> and the IAM<br>
binding in Step 5 was applied successfully.</p>
</blockquote>
<h2 id="step-7-generate-kubeconfig-files">Step 7: Generate kubeconfig files<a class="anchor-link" id="step-7-generate-kubeconfig-files"></a></h2>
<blockquote>
<p><strong>Security:</strong> Kubeconfig files contain credentials that grant access to your clusters.<br>
Keep both files in <code>~/.kube/psmdb-demo</code> only, do not copy them elsewhere, commit them<br>
to version control, or share them with anyone.</p>
</blockquote>
<p>Store kubeconfig files in a dedicated directory outside this project:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-13">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p ~/.kube/psmdb-demo
</span></span><span class="line"><span class="cl">chmod <span class="m">700</span> ~/.kube/psmdb-demo</span></span></code></pre>
</div>
</div>
</div>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-14">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Generate kubeconfig for main cluster</span>
</span></span><span class="line"><span class="cl"><span class="nv">KUBECONFIG</span><span class="o">=</span>~/.kube/psmdb-demo/gcp-main_config gcloud container clusters <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> get-credentials main-cluster --zone us-central1-a
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Generate kubeconfig for replica cluster</span>
</span></span><span class="line"><span class="cl"><span class="nv">KUBECONFIG</span><span class="o">=</span>~/.kube/psmdb-demo/gcp-replica_config gcloud container clusters <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> get-credentials replica-cluster --zone us-central1-a
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">chmod <span class="m">600</span> ~/.kube/psmdb-demo/gcp-main_config ~/.kube/psmdb-demo/gcp-replica_config</span></span></code></pre>
</div>
</div>
</div>
<p>Verify both files were created:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-15">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ls -la ~/.kube/psmdb-demo/gcp-main_config ~/.kube/psmdb-demo/gcp-replica_config</span></span></code></pre>
</div>
</div>
</div>
<p>Verify each connects to the correct cluster:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-16" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-16">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl --kubeconfig ~/.kube/psmdb-demo/gcp-main_config get nodes
</span></span><span class="line"><span class="cl">kubectl --kubeconfig ~/.kube/psmdb-demo/gcp-replica_config get nodes</span></span></code></pre>
</div>
</div>
</div>
<blockquote>
<p><strong>Two terminals, set up once:</strong> Open <strong>two terminal windows</strong> for the rest of this<br>
guide. Run each export <strong>once</strong> when you open the terminal, you do not need to repeat<br>
it in later steps unless you open a new window:</p>
<table>
<thead>
<tr>
<th>Terminal</th>
<th>Cluster</th>
<th>Run once when opening the terminal</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Terminal 1</strong></td>
<td>Main</td>
<td><code>export KUBECONFIG=~/.kube/psmdb-demo/gcp-main_config</code></td>
</tr>
<tr>
<td><strong>Terminal 2</strong></td>
<td>Replica</td>
<td><code>export KUBECONFIG=~/.kube/psmdb-demo/gcp-replica_config</code></td>
</tr>
</tbody>
</table>
<p>Verify:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-17" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-17">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get nodes</span></span></code></pre>
</div>
</div>
</div>
<p>From Step 8 onward, every <code>kubectl</code> block is labeled <strong>Terminal 1</strong> or <strong>Terminal 2</strong><br>
only. Run the command in the matching terminal.<br>
Re-export only if you open a <strong>new</strong> terminal window.</p>
</blockquote>
<p>Example: This is how the cluster looks like:</p>
<p><strong>Terminal 1 &middot; main cluster</strong></p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-18" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-18">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ kubectl get nodes
</span></span><span class="line"><span class="cl">NAME STATUS ROLES AGE VERSION
</span></span><span class="line"><span class="cl">gke-main-cluster-default-pool-9c0082b4-19wj Ready  68m v1.35.3-gke.2190000
</span></span><span class="line"><span class="cl">gke-main-cluster-default-pool-9c0082b4-q78p Ready  68m v1.35.3-gke.2190000
</span></span><span class="line"><span class="cl">gke-main-cluster-default-pool-9c0082b4-rb6r Ready  68m v1.35.3-gke.2190000</span></span></code></pre>
</div>
</div>
</div>
<p><strong>Terminal 2 &middot; replica cluster</strong></p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-19" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-19">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ kubectl get nodes
</span></span><span class="line"><span class="cl">NAME STATUS ROLES AGE VERSION
</span></span><span class="line"><span class="cl">gke-replica-cluster-default-pool-3f3e6f2b-1qkb Ready  56m v1.35.3-gke.2190000
</span></span><span class="line"><span class="cl">gke-replica-cluster-default-pool-3f3e6f2b-gl5j Ready  56m v1.35.3-gke.2190000
</span></span><span class="line"><span class="cl">gke-replica-cluster-default-pool-3f3e6f2b-h6hk Ready  56m v1.35.3-gke.2190000</span></span></code></pre>
</div>
</div>
</div>
<h2 id="step-8-grant-cluster-admin-permissions-to-your-account">Step 8: Grant cluster-admin permissions to your account<a class="anchor-link" id="step-8-grant-cluster-admin-permissions-to-your-account"></a></h2>
<p>GCP project access and Kubernetes permissions inside each cluster are separate,<br>
Step 7&rsquo;s kubeconfig lets you authenticate, but from Step 9 onward you need<br>
cluster-wide rights to install the operator and deploy MongoDB. Main and replica are<br>
independent clusters with their own RBAC, so run the same command on each; a binding<br>
on one does not apply to the other.</p>
<p><strong>Terminal 1:</strong></p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-20" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-20">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl create clusterrolebinding cluster-admin-binding <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --clusterrole cluster-admin <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --user <span class="k">$(</span>gcloud config get-value core/account<span class="k">)</span></span></span></code></pre>
</div>
</div>
</div>
<p><strong>Terminal 2:</strong></p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-21" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-21">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl create clusterrolebinding cluster-admin-binding <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --clusterrole cluster-admin <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --user <span class="k">$(</span>gcloud config get-value core/account<span class="k">)</span></span></span></code></pre>
</div>
</div>
</div>
<blockquote>
<p>If you see <code>AlreadyExists</code> on either cluster, the binding was already created in a<br>
previous session. This is not an error; continue to the next step.</p>
</blockquote>
<p>Verify on both clusters, each should return <code>yes</code>:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-22" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-22">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl auth can-i <span class="s1">'*'</span> <span class="s1">'*'</span> --all-namespaces <span class="c1"># Terminal 1</span>
</span></span><span class="line"><span class="cl">kubectl auth can-i <span class="s1">'*'</span> <span class="s1">'*'</span> --all-namespaces <span class="c1"># Terminal 2</span></span></span></code></pre>
</div>
</div>
</div>
<hr>
<h2 id="step-9-create-namespace-and-install-the-operator-on-both-clusters">Step 9: Create namespace and install the Operator on both clusters<a class="anchor-link" id="step-9-create-namespace-and-install-the-operator-on-both-clusters"></a></h2>
<p>The namespace <strong>must be identical on both clusters</strong>. The MCS DNS name includes<br>
the namespace (e.g. <code>rs0.psmdb.svc.clusterset.local</code>). If the namespaces differ,<br>
nodes cannot find each other.</p>
<p><strong>Terminal 1 (main cluster):</strong></p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-23" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-23">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl create namespace psmdb
</span></span><span class="line"><span class="cl">kubectl config set-context --current --namespace<span class="o">=</span>psmdb
</span></span><span class="line"><span class="cl">kubectl apply --server-side <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -f https://raw.githubusercontent.com/percona/percona-server-mongodb-operator/v1.20.1/deploy/bundle.yaml <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -n psmdb</span></span></code></pre>
</div>
</div>
</div>
<p><strong>Terminal 2 (replica cluster):</strong></p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-24" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-24">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl create namespace psmdb
</span></span><span class="line"><span class="cl">kubectl config set-context --current --namespace<span class="o">=</span>psmdb
</span></span><span class="line"><span class="cl">kubectl apply --server-side <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -f https://raw.githubusercontent.com/percona/percona-server-mongodb-operator/v1.20.1/deploy/bundle.yaml <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -n psmdb</span></span></code></pre>
</div>
</div>
</div>
<p>Verify the Operator is running on each cluster:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-25" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-25">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Terminal 1</span>
</span></span><span class="line"><span class="cl">kubectl get pods
</span></span><span class="line"><span class="cl">NAME READY STATUS RESTARTS AGE
</span></span><span class="line"><span class="cl">percona-server-mongodb-operator-6877fcf797-stv4s 1/1 Running <span class="m">0</span> 33s
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Terminal 2</span>
</span></span><span class="line"><span class="cl">kubectl get pods
</span></span><span class="line"><span class="cl">NAME READY STATUS RESTARTS AGE
</span></span><span class="line"><span class="cl">percona-server-mongodb-operator-6877fcf797-gslpz 1/1 Running <span class="m">0</span> 9s</span></span></code></pre>
</div>
</div>
</div>
<h2 id="step-10-create-the-main-cluster">Step 10: Create the Main cluster<a class="anchor-link" id="step-10-create-the-main-cluster"></a></h2>
<p>Run all commands in <strong>Terminal 1</strong> (main cluster).</p>
<p>Create <code>cr-main.yaml</code>:</p>
<blockquote>
<p><strong>Important notes:</strong></p>
<ul>
<li><code>type: ClusterIP</code> is <strong>required</strong> for MCS, LoadBalancer will not work</li>
<li><code>multiCluster.DNSSuffix: svc.clusterset.local</code> enables cross-cluster DNS</li>
<li><code>crVersion: 1.20.1</code>, use a released version only. The Operator derives the<br>
init container image tag from <code>crVersion</code>.</li>
</ul>
</blockquote>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-26" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-26">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat &gt; cr-main.yaml <span class="s">&lt;&lt; 'EOF'
</span></span></span><span class="line"><span class="cl"><span class="s">apiVersion: psmdb.percona.com/v1
</span></span></span><span class="line"><span class="cl"><span class="s">kind: PerconaServerMongoDB
</span></span></span><span class="line"><span class="cl"><span class="s">metadata:
</span></span></span><span class="line"><span class="cl"><span class="s"> name: main-cluster
</span></span></span><span class="line"><span class="cl"><span class="s">spec:
</span></span></span><span class="line"><span class="cl"><span class="s"> crVersion: 1.20.1
</span></span></span><span class="line"><span class="cl"><span class="s"> image: percona/percona-server-mongodb:7.0.14-8-multi
</span></span></span><span class="line"><span class="cl"><span class="s"> updateStrategy: SmartUpdate
</span></span></span><span class="line"><span class="cl"><span class="s"> multiCluster:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> DNSSuffix: svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> upgradeOptions:
</span></span></span><span class="line"><span class="cl"><span class="s"> apply: disabled
</span></span></span><span class="line"><span class="cl"><span class="s"> schedule: "0 2 * * *"
</span></span></span><span class="line"><span class="cl"><span class="s"> secrets:
</span></span></span><span class="line"><span class="cl"><span class="s"> users: my-cluster-name-secrets
</span></span></span><span class="line"><span class="cl"><span class="s"> encryptionKey: my-cluster-name-mongodb-encryption-key
</span></span></span><span class="line"><span class="cl"><span class="s"> replsets:
</span></span></span><span class="line"><span class="cl"><span class="s"> - name: rs0
</span></span></span><span class="line"><span class="cl"><span class="s"> size: 3
</span></span></span><span class="line"><span class="cl"><span class="s"> expose:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> type: ClusterIP
</span></span></span><span class="line"><span class="cl"><span class="s"> volumeSpec:
</span></span></span><span class="line"><span class="cl"><span class="s"> persistentVolumeClaim:
</span></span></span><span class="line"><span class="cl"><span class="s"> resources:
</span></span></span><span class="line"><span class="cl"><span class="s"> requests:
</span></span></span><span class="line"><span class="cl"><span class="s"> storage: 3Gi
</span></span></span><span class="line"><span class="cl"><span class="s"> sharding:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> configsvrReplSet:
</span></span></span><span class="line"><span class="cl"><span class="s"> size: 3
</span></span></span><span class="line"><span class="cl"><span class="s"> expose:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> type: ClusterIP
</span></span></span><span class="line"><span class="cl"><span class="s"> volumeSpec:
</span></span></span><span class="line"><span class="cl"><span class="s"> persistentVolumeClaim:
</span></span></span><span class="line"><span class="cl"><span class="s"> resources:
</span></span></span><span class="line"><span class="cl"><span class="s"> requests:
</span></span></span><span class="line"><span class="cl"><span class="s"> storage: 3Gi
</span></span></span><span class="line"><span class="cl"><span class="s"> mongos:
</span></span></span><span class="line"><span class="cl"><span class="s"> size: 3
</span></span></span><span class="line"><span class="cl"><span class="s"> expose:
</span></span></span><span class="line"><span class="cl"><span class="s"> type: ClusterIP
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span></span></span></code></pre>
</div>
</div>
</div>
<p>Apply it:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-27" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-27">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl apply -f cr-main.yaml -n psmdb</span></span></code></pre>
</div>
</div>
</div>
<p>Watch until status is <code>ready</code> (takes 3&ndash;5 minutes):</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-28" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-28">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get psmdb -n psmdb -w</span></span></code></pre>
</div>
</div>
</div>
<p>Expected output:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-29" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-29">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">kubectl get psmdb -n psmdb
</span></span><span class="line"><span class="cl">NAME ENDPOINT STATUS AGE
</span></span><span class="line"><span class="cl">main-cluster main-cluster-mongos.psmdb.svc.cluster.local:27017 ready 13m</span></span></code></pre>
</div>
</div>
</div>
<p>Verify ServiceExport resources were created (takes up to 5 minutes after ready):</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-30" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-30">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get serviceexport -n psmdb</span></span></code></pre>
</div>
</div>
</div>
<p>Expected output:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-31" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-31">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">NAME AGE
</span></span><span class="line"><span class="cl">main-cluster-cfg 27m
</span></span><span class="line"><span class="cl">main-cluster-cfg-0 27m
</span></span><span class="line"><span class="cl">main-cluster-cfg-1 27m
</span></span><span class="line"><span class="cl">main-cluster-cfg-2 26m
</span></span><span class="line"><span class="cl">main-cluster-mongos 27m
</span></span><span class="line"><span class="cl">main-cluster-rs0 27m
</span></span><span class="line"><span class="cl">main-cluster-rs0-0 27m
</span></span><span class="line"><span class="cl">main-cluster-rs0-1 27m
</span></span><span class="line"><span class="cl">main-cluster-rs0-2 26m</span></span></code></pre>
</div>
</div>
</div>
<h2 id="step-11-export-secrets-from-the-main-cluster">Step 11: Export secrets from the Main cluster<a class="anchor-link" id="step-11-export-secrets-from-the-main-cluster"></a></h2>
<p>Run all commands in <strong>Terminal 1</strong> (main cluster).</p>
<p>The Replica cluster runs in <code>unmanaged: true</code> mode and cannot generate its own<br>
TLS certificates or credentials. It must receive exact copies of the Main cluster secrets:</p>
<ul>
<li>Without TLS secrets &rarr; pods never start</li>
<li>Without user credentials &rarr; pods start but fail liveness checks and restart continuously</li>
</ul>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-32" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-32">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get secret my-cluster-name-secrets -n psmdb -o yaml &gt; my-cluster-secrets.yml
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kubectl get secret main-cluster-ssl -n psmdb -o yaml &gt; main-cluster-ssl.yml
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kubectl get secret main-cluster-ssl-internal -n psmdb -o yaml &gt; main-cluster-ssl-internal.yml
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kubectl get secret my-cluster-name-mongodb-encryption-key -n psmdb -o yaml &gt; <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span>my-cluster-name-mongodb-encryption-key.yml</span></span></code></pre>
</div>
</div>
</div>
<h2 id="step-12-modify-secrets-for-the-replica-cluster">Step 12: Modify secrets for the Replica cluster<a class="anchor-link" id="step-12-modify-secrets-for-the-replica-cluster"></a></h2>
<p>The exported secrets contain cluster-specific metadata that must be removed before<br>
applying to another cluster. The <code>resourceVersion</code> and <code>uid</code> fields are unique to the<br>
Main cluster and cause a conflict error if reused unchanged.</p>
<p>The secret <strong>data</strong> (passwords, TLS certificates, encryption key) is copied as-is,<br>
the replica must use the same credentials to join the same MongoDB deployment. The<br>
Kubernetes secret <strong>names</strong> for user credentials and the encryption key stay the same<br>
(<code>my-cluster-name-secrets</code>, <code>my-cluster-name-mongodb-encryption-key</code>) because<br>
<code>cr-replica.yaml</code> references those exact names. Only the TLS secrets are renamed<br>
(<code>main-cluster-ssl</code> &rarr; <code>replica-cluster-ssl</code>) via <code>sed</code>; the <code>yq</code> step strips stale<br>
metadata, it does not rename those two secrets.</p>
<blockquote>
<p><strong>Linux vs macOS:</strong> <code>sed -i ''</code> is macOS-only syntax.<br>
On Linux, use <code>sed -i</code> without the empty string argument.</p>
</blockquote>
<p><strong>Terminal 1 (main cluster)</strong>, modify the exported files locally:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-33" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-33">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Secret 1, user credentials</span>
</span></span><span class="line"><span class="cl">yq <span class="nb">eval</span> <span class="s1">'del(.metadata.ownerReferences, .metadata.annotations,
</span></span></span><span class="line"><span class="cl"><span class="s1"> .metadata.creationTimestamp, .metadata.resourceVersion,
</span></span></span><span class="line"><span class="cl"><span class="s1"> .metadata.selfLink, .metadata.uid)'</span> <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> my-cluster-secrets.yml &gt; my-cluster-secrets-replica.yaml
</span></span><span class="line"><span class="cl">sed -i <span class="s1">'s/main-cluster/replica-cluster/g'</span> my-cluster-secrets-replica.yaml
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Secret 2, SSL client certificates</span>
</span></span><span class="line"><span class="cl">yq <span class="nb">eval</span> <span class="s1">'del(.metadata.ownerReferences, .metadata.annotations,
</span></span></span><span class="line"><span class="cl"><span class="s1"> .metadata.creationTimestamp, .metadata.resourceVersion,
</span></span></span><span class="line"><span class="cl"><span class="s1"> .metadata.selfLink, .metadata.uid)'</span> <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> main-cluster-ssl.yml &gt; replica-cluster-ssl.yml
</span></span><span class="line"><span class="cl">sed -i <span class="s1">'s/main-cluster/replica-cluster/g'</span> replica-cluster-ssl.yml
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Secret 3, SSL internal replication certificates</span>
</span></span><span class="line"><span class="cl">yq <span class="nb">eval</span> <span class="s1">'del(.metadata.ownerReferences, .metadata.annotations,
</span></span></span><span class="line"><span class="cl"><span class="s1"> .metadata.creationTimestamp, .metadata.resourceVersion,
</span></span></span><span class="line"><span class="cl"><span class="s1"> .metadata.selfLink, .metadata.uid)'</span> <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> main-cluster-ssl-internal.yml &gt; replica-cluster-ssl-internal.yml
</span></span><span class="line"><span class="cl">sed -i <span class="s1">'s/main-cluster/replica-cluster/g'</span> replica-cluster-ssl-internal.yml
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Secret 4, encryption key</span>
</span></span><span class="line"><span class="cl">yq <span class="nb">eval</span> <span class="s1">'del(.metadata.ownerReferences, .metadata.annotations,
</span></span></span><span class="line"><span class="cl"><span class="s1"> .metadata.creationTimestamp, .metadata.resourceVersion,
</span></span></span><span class="line"><span class="cl"><span class="s1"> .metadata.selfLink, .metadata.uid)'</span> <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> my-cluster-name-mongodb-encryption-key.yml &gt; <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> my-cluster-name-mongodb-encryption-key-replica.yml
</span></span><span class="line"><span class="cl">sed -i <span class="s1">'s/main-cluster/replica-cluster/g'</span> <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> my-cluster-name-mongodb-encryption-key-replica.yml</span></span></code></pre>
</div>
</div>
</div>
<blockquote>
<p><strong>Important:</strong> If you delete and recreate the Main cluster, re-export all four<br>
secrets before applying to the Replica. The <code>resourceVersion</code> and <code>uid</code> change<br>
on every cluster recreation, stale values cause a conflict error.</p>
</blockquote>
<p><strong>Terminal 2 (replica cluster)</strong>, apply and verify:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-34" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-34">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl apply -f my-cluster-secrets-replica.yaml -n psmdb
</span></span><span class="line"><span class="cl">kubectl apply -f replica-cluster-ssl.yml -n psmdb
</span></span><span class="line"><span class="cl">kubectl apply -f replica-cluster-ssl-internal.yml -n psmdb
</span></span><span class="line"><span class="cl">kubectl apply -f my-cluster-name-mongodb-encryption-key-replica.yml -n psmdb
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kubectl get secrets -n psmdb</span></span></code></pre>
</div>
</div>
</div>
<p>Expected output should include:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-35" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-35">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">NAME TYPE DATA AGE
</span></span><span class="line"><span class="cl">my-cluster-name-mongodb-encryption-key Opaque 1 8s
</span></span><span class="line"><span class="cl">my-cluster-name-secrets Opaque 10 33s
</span></span><span class="line"><span class="cl">replica-cluster-ssl kubernetes.io/tls 3 24s
</span></span><span class="line"><span class="cl">replica-cluster-ssl-internal kubernetes.io/tls 3 16s</span></span></code></pre>
</div>
</div>
</div>
<h2 id="step-13-create-the-replica-cluster">Step 13: Create the Replica cluster<a class="anchor-link" id="step-13-create-the-replica-cluster"></a></h2>
<p>Run all commands in <strong>Terminal 2</strong> (replica cluster).</p>
<p>Create <code>cr-replica.yaml</code>:</p>
<blockquote>
<p><strong>Key differences from cr-main.yaml:</strong></p>
<ul>
<li><code>unmanaged: true</code> prevents the Operator from initializing a new replica set,<br>
avoiding split-brain with the Main cluster&rsquo;s Operator</li>
<li><code>updateStrategy: RollingUpdate</code>, SmartUpdate is not supported on unmanaged clusters</li>
<li>SSL secrets are explicitly referenced because the Operator does not generate them here</li>
</ul>
</blockquote>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-36" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-36">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat &gt; cr-replica.yaml <span class="s">&lt;&lt; 'EOF'
</span></span></span><span class="line"><span class="cl"><span class="s">apiVersion: psmdb.percona.com/v1
</span></span></span><span class="line"><span class="cl"><span class="s">kind: PerconaServerMongoDB
</span></span></span><span class="line"><span class="cl"><span class="s">metadata:
</span></span></span><span class="line"><span class="cl"><span class="s"> name: replica-cluster
</span></span></span><span class="line"><span class="cl"><span class="s">spec:
</span></span></span><span class="line"><span class="cl"><span class="s"> unmanaged: true
</span></span></span><span class="line"><span class="cl"><span class="s"> crVersion: 1.20.1
</span></span></span><span class="line"><span class="cl"><span class="s"> image: percona/percona-server-mongodb:7.0.14-8-multi
</span></span></span><span class="line"><span class="cl"><span class="s"> updateStrategy: RollingUpdate
</span></span></span><span class="line"><span class="cl"><span class="s"> multiCluster:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> DNSSuffix: svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> upgradeOptions:
</span></span></span><span class="line"><span class="cl"><span class="s"> apply: disabled
</span></span></span><span class="line"><span class="cl"><span class="s"> schedule: "0 2 * * *"
</span></span></span><span class="line"><span class="cl"><span class="s"> secrets:
</span></span></span><span class="line"><span class="cl"><span class="s"> users: my-cluster-name-secrets
</span></span></span><span class="line"><span class="cl"><span class="s"> encryptionKey: my-cluster-name-mongodb-encryption-key
</span></span></span><span class="line"><span class="cl"><span class="s"> ssl: replica-cluster-ssl
</span></span></span><span class="line"><span class="cl"><span class="s"> sslInternal: replica-cluster-ssl-internal
</span></span></span><span class="line"><span class="cl"><span class="s"> replsets:
</span></span></span><span class="line"><span class="cl"><span class="s"> - name: rs0
</span></span></span><span class="line"><span class="cl"><span class="s"> size: 3
</span></span></span><span class="line"><span class="cl"><span class="s"> expose:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> type: ClusterIP
</span></span></span><span class="line"><span class="cl"><span class="s"> volumeSpec:
</span></span></span><span class="line"><span class="cl"><span class="s"> persistentVolumeClaim:
</span></span></span><span class="line"><span class="cl"><span class="s"> resources:
</span></span></span><span class="line"><span class="cl"><span class="s"> requests:
</span></span></span><span class="line"><span class="cl"><span class="s"> storage: 3Gi
</span></span></span><span class="line"><span class="cl"><span class="s"> sharding:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> configsvrReplSet:
</span></span></span><span class="line"><span class="cl"><span class="s"> size: 3
</span></span></span><span class="line"><span class="cl"><span class="s"> expose:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> type: ClusterIP
</span></span></span><span class="line"><span class="cl"><span class="s"> volumeSpec:
</span></span></span><span class="line"><span class="cl"><span class="s"> persistentVolumeClaim:
</span></span></span><span class="line"><span class="cl"><span class="s"> resources:
</span></span></span><span class="line"><span class="cl"><span class="s"> requests:
</span></span></span><span class="line"><span class="cl"><span class="s"> storage: 3Gi
</span></span></span><span class="line"><span class="cl"><span class="s"> mongos:
</span></span></span><span class="line"><span class="cl"><span class="s"> size: 3
</span></span></span><span class="line"><span class="cl"><span class="s"> expose:
</span></span></span><span class="line"><span class="cl"><span class="s"> type: ClusterIP
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span></span></span></code></pre>
</div>
</div>
</div>
<p>Apply it:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-37" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-37">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl apply -f cr-replica.yaml -n psmdb</span></span></code></pre>
</div>
</div>
</div>
<p>Watch until status is <code>ready</code>:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-38" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-38">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get psmdb -n psmdb -w</span></span></code></pre>
</div>
</div>
</div>
<p>Expected output:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-39" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-39">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get pods
</span></span><span class="line"><span class="cl">NAME READY STATUS RESTARTS AGE
</span></span><span class="line"><span class="cl">percona-server-mongodb-operator-6877fcf797-gslpz 1/1 Running <span class="m">0</span> 119m
</span></span><span class="line"><span class="cl">replica-cluster-cfg-0 1/1 Running <span class="m">11</span> <span class="o">(</span>25s ago<span class="o">)</span> 43m
</span></span><span class="line"><span class="cl">replica-cluster-cfg-1 1/1 Running <span class="m">10</span> <span class="o">(</span>7m49s ago<span class="o">)</span> 43m
</span></span><span class="line"><span class="cl">replica-cluster-cfg-2 1/1 Running <span class="m">10</span> <span class="o">(</span>7m25s ago<span class="o">)</span> 42m
</span></span><span class="line"><span class="cl">replica-cluster-mongos-0 0/1 Running <span class="m">10</span> <span class="o">(</span>6m46s ago<span class="o">)</span> 42m
</span></span><span class="line"><span class="cl">replica-cluster-rs0-0 1/1 Running <span class="m">11</span> <span class="o">(</span>22s ago<span class="o">)</span> 43m
</span></span><span class="line"><span class="cl">replica-cluster-rs0-1 1/1 Running <span class="m">10</span> <span class="o">(</span>7m17s ago<span class="o">)</span> 43m
</span></span><span class="line"><span class="cl">replica-cluster-rs0-2 1/1 Running <span class="m">10</span> <span class="o">(</span>7m21s ago<span class="o">)</span> 42m</span></span></code></pre>
</div>
</div>
</div>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-40" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-40">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get pods
</span></span><span class="line"><span class="cl">NAME READY STATUS RESTARTS AGE
</span></span><span class="line"><span class="cl">percona-server-mongodb-operator-6877fcf797-gslpz 1/1 Running <span class="m">0</span> 113m
</span></span><span class="line"><span class="cl">replica-cluster-cfg-0 0/1 CrashLoopBackOff <span class="m">9</span> <span class="o">(</span>108s ago<span class="o">)</span> 37m
</span></span><span class="line"><span class="cl">replica-cluster-cfg-1 0/1 CrashLoopBackOff <span class="m">9</span> <span class="o">(</span>72s ago<span class="o">)</span> 36m
</span></span><span class="line"><span class="cl">replica-cluster-cfg-2 0/1 CrashLoopBackOff <span class="m">9</span> <span class="o">(</span>48s ago<span class="o">)</span> 36m
</span></span><span class="line"><span class="cl">replica-cluster-mongos-0 0/1 CrashLoopBackOff <span class="m">9</span> <span class="o">(</span>9s ago<span class="o">)</span> 36m
</span></span><span class="line"><span class="cl">replica-cluster-rs0-0 0/1 CrashLoopBackOff <span class="m">9</span> <span class="o">(</span>104s ago<span class="o">)</span> 37m
</span></span><span class="line"><span class="cl">replica-cluster-rs0-1 0/1 CrashLoopBackOff <span class="m">9</span> <span class="o">(</span>40s ago<span class="o">)</span> 36m
</span></span><span class="line"><span class="cl">replica-cluster-rs0-2 0/1 CrashLoopBackOff <span class="m">9</span> <span class="o">(</span>44s ago<span class="o">)</span> 36m</span></span></code></pre>
</div>
</div>
</div>
<blockquote>
<p><strong>Expected behavior before interconnect (Step 15):</strong> The replica cluster runs with<br>
<code>unmanaged: true</code>, so the Operator starts mongoc pods but does <strong>not</strong> initialize a<br>
separate replica set, that happens on the main cluster after you add <code>externalNodes</code><br>
in Step 15. While waiting, replica pods may show <code>CrashLoopBackOff</code> with many<br>
restarts. This is usually the liveness probe timing out, not mongoc crashing. It is<br>
common for <code>cfg</code> and <code>rs0</code> pods to settle to <code>1/1 Running</code> before interconnect;<br>
<code>mongos</code> often stays <code>0/1</code> the longest. <code>kubectl get psmdb</code> may not show <code>ready</code><br>
yet, that is expected. Continue to Steps 14 and 15.<br>
If pods keep restarting <strong>after</strong> Step 15, re-check the secrets from Steps 11&ndash;12.</p>
</blockquote>
<p>Verify ServiceExport resources were created (takes up to 5 minutes after ready):</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-41" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-41">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get serviceexport -n psmdb</span></span></code></pre>
</div>
</div>
</div>
<p>Expected output:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-42" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-42">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">NAME AGE
</span></span><span class="line"><span class="cl">replica-cluster-cfg 59m
</span></span><span class="line"><span class="cl">replica-cluster-cfg-0 59m
</span></span><span class="line"><span class="cl">replica-cluster-cfg-1 58m
</span></span><span class="line"><span class="cl">replica-cluster-cfg-2 58m
</span></span><span class="line"><span class="cl">replica-cluster-mongos 59m
</span></span><span class="line"><span class="cl">replica-cluster-rs0 59m
</span></span><span class="line"><span class="cl">replica-cluster-rs0-0 59m
</span></span><span class="line"><span class="cl">replica-cluster-rs0-1 58m
</span></span><span class="line"><span class="cl">replica-cluster-rs0-2 57m</span></span></code></pre>
</div>
</div>
</div>
<h2 id="step-14-verify-serviceimports-on-both-clusters">Step 14: Verify ServiceImports on both clusters<a class="anchor-link" id="step-14-verify-serviceimports-on-both-clusters"></a></h2>
<p>After both clusters are running, the MCS controller creates <code>ServiceImport</code> objects<br>
automatically. This takes approximately 5 minutes after the ServiceExports appear.</p>
<p><strong>Terminal 1 (main cluster):</strong></p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-43" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-43">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get serviceimport -n psmdb</span></span></code></pre>
</div>
</div>
</div>
<p><strong>Terminal 2 (replica cluster):</strong></p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-44" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-44">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get serviceimport -n psmdb</span></span></code></pre>
</div>
</div>
</div>
<p>Each cluster should show <strong>18 total ServiceImports</strong>, 9 for each cluster.<br>
Example output on the replica cluster:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-45" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-45">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">NAME TYPE IP AGE
</span></span><span class="line"><span class="cl">main-cluster-cfg Headless 127m
</span></span><span class="line"><span class="cl">main-cluster-cfg-0 ClusterSetIP ["34.118.239.158"] 127m
</span></span><span class="line"><span class="cl">main-cluster-cfg-1 ClusterSetIP ["34.118.230.45"] 125m
</span></span><span class="line"><span class="cl">main-cluster-cfg-2 ClusterSetIP ["34.118.237.3"] 123m
</span></span><span class="line"><span class="cl">main-cluster-mongos ClusterSetIP ["34.118.230.127"] 127m
</span></span><span class="line"><span class="cl">main-cluster-rs0 Headless 127m
</span></span><span class="line"><span class="cl">main-cluster-rs0-0 ClusterSetIP ["34.118.237.28"] 127m
</span></span><span class="line"><span class="cl">main-cluster-rs0-1 ClusterSetIP ["34.118.230.37"] 125m
</span></span><span class="line"><span class="cl">main-cluster-rs0-2 ClusterSetIP ["34.118.226.30"] 123m
</span></span><span class="line"><span class="cl">replica-cluster-cfg Headless 62m
</span></span><span class="line"><span class="cl">replica-cluster-cfg-0 ClusterSetIP ["34.118.231.166"] 62m
</span></span><span class="line"><span class="cl">replica-cluster-cfg-1 ClusterSetIP ["34.118.234.146"] 59m
</span></span><span class="line"><span class="cl">replica-cluster-cfg-2 ClusterSetIP ["34.118.225.208"] 59m
</span></span><span class="line"><span class="cl">replica-cluster-mongos ClusterSetIP ["34.118.239.237"] 62m
</span></span><span class="line"><span class="cl">replica-cluster-rs0 Headless 62m
</span></span><span class="line"><span class="cl">replica-cluster-rs0-0 ClusterSetIP ["34.118.228.53"] 62m
</span></span><span class="line"><span class="cl">replica-cluster-rs0-1 ClusterSetIP ["34.118.238.50"] 59m
</span></span><span class="line"><span class="cl">replica-cluster-rs0-2 ClusterSetIP ["34.118.232.241"] 59m</span></span></code></pre>
</div>
</div>
</div>
<p>If any are missing, check the MCS importer logs on the affected cluster:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-46" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-46">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl logs -n gke-mcs -l k8s-app<span class="o">=</span>gke-mcs-importer --tail<span class="o">=</span><span class="m">30</span> <span class="c1"># run in Terminal 1 or 2</span></span></span></code></pre>
</div>
</div>
</div>
<h2 id="step-15-interconnect-the-clusters-add-externalnodes">Step 15: Interconnect the clusters (add externalNodes)<a class="anchor-link" id="step-15-interconnect-the-clusters-add-externalnodes"></a></h2>
<p><code>ServiceImport</code> objects give each cluster a way to resolve DNS names for services<br>
in other clusters. <code>externalNodes</code> tells MongoDB to actually use those addresses<br>
as replica set members. Both are needed, ServiceImport is the phone book,<br>
externalNodes is the instruction to call.</p>
<p><strong>Why two voting and one non-voting external node?</strong><br>
Adding two voting nodes (<code>votes: 1</code>) and one non-voting node (<code>votes: 0</code>) from the<br>
other site prevents split-brain. If the network between sites is severed, neither<br>
side can accidentally promote a new Primary using only its external nodes.</p>
<h3 id="15a-add-replica-nodes-to-main-cluster">15a: Add Replica nodes to Main cluster<a class="anchor-link" id="15a-add-replica-nodes-to-main-cluster"></a></h3>
<p>Run in <strong>Terminal 1</strong> (main cluster).</p>
<p>Copy <code>cr-main.yaml</code> to <code>cr-main-after.yaml</code> and add an <strong><code>externalNodes</code></strong> block under<br>
<code>replsets.rs0</code> and under <code>sharding.configsvrReplSet</code>, everything else stays the same.</p>
<p>Create <code>cr-main-after.yaml</code>:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-47" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-47">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat &gt; cr-main-after.yaml <span class="s">&lt;&lt; 'EOF'
</span></span></span><span class="line"><span class="cl"><span class="s">apiVersion: psmdb.percona.com/v1
</span></span></span><span class="line"><span class="cl"><span class="s">kind: PerconaServerMongoDB
</span></span></span><span class="line"><span class="cl"><span class="s">metadata:
</span></span></span><span class="line"><span class="cl"><span class="s"> name: main-cluster
</span></span></span><span class="line"><span class="cl"><span class="s">spec:
</span></span></span><span class="line"><span class="cl"><span class="s"> crVersion: 1.20.1
</span></span></span><span class="line"><span class="cl"><span class="s"> image: percona/percona-server-mongodb:7.0.14-8-multi
</span></span></span><span class="line"><span class="cl"><span class="s"> updateStrategy: SmartUpdate
</span></span></span><span class="line"><span class="cl"><span class="s"> multiCluster:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> DNSSuffix: svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> upgradeOptions:
</span></span></span><span class="line"><span class="cl"><span class="s"> apply: disabled
</span></span></span><span class="line"><span class="cl"><span class="s"> schedule: "0 2 * * *"
</span></span></span><span class="line"><span class="cl"><span class="s"> secrets:
</span></span></span><span class="line"><span class="cl"><span class="s"> users: my-cluster-name-secrets
</span></span></span><span class="line"><span class="cl"><span class="s"> encryptionKey: my-cluster-name-mongodb-encryption-key
</span></span></span><span class="line"><span class="cl"><span class="s"> replsets:
</span></span></span><span class="line"><span class="cl"><span class="s"> - name: rs0
</span></span></span><span class="line"><span class="cl"><span class="s"> size: 3
</span></span></span><span class="line"><span class="cl"><span class="s"> externalNodes:
</span></span></span><span class="line"><span class="cl"><span class="s"> - host: replica-cluster-rs0-0.psmdb.svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> votes: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> priority: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> - host: replica-cluster-rs0-1.psmdb.svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> votes: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> priority: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> - host: replica-cluster-rs0-2.psmdb.svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> votes: 0
</span></span></span><span class="line"><span class="cl"><span class="s"> priority: 0
</span></span></span><span class="line"><span class="cl"><span class="s"> expose:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> type: ClusterIP
</span></span></span><span class="line"><span class="cl"><span class="s"> volumeSpec:
</span></span></span><span class="line"><span class="cl"><span class="s"> persistentVolumeClaim:
</span></span></span><span class="line"><span class="cl"><span class="s"> resources:
</span></span></span><span class="line"><span class="cl"><span class="s"> requests:
</span></span></span><span class="line"><span class="cl"><span class="s"> storage: 3Gi
</span></span></span><span class="line"><span class="cl"><span class="s"> sharding:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> configsvrReplSet:
</span></span></span><span class="line"><span class="cl"><span class="s"> size: 3
</span></span></span><span class="line"><span class="cl"><span class="s"> externalNodes:
</span></span></span><span class="line"><span class="cl"><span class="s"> - host: replica-cluster-cfg-0.psmdb.svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> votes: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> priority: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> - host: replica-cluster-cfg-1.psmdb.svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> votes: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> priority: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> - host: replica-cluster-cfg-2.psmdb.svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> votes: 0
</span></span></span><span class="line"><span class="cl"><span class="s"> priority: 0
</span></span></span><span class="line"><span class="cl"><span class="s"> expose:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> type: ClusterIP
</span></span></span><span class="line"><span class="cl"><span class="s"> volumeSpec:
</span></span></span><span class="line"><span class="cl"><span class="s"> persistentVolumeClaim:
</span></span></span><span class="line"><span class="cl"><span class="s"> resources:
</span></span></span><span class="line"><span class="cl"><span class="s"> requests:
</span></span></span><span class="line"><span class="cl"><span class="s"> storage: 3Gi
</span></span></span><span class="line"><span class="cl"><span class="s"> mongos:
</span></span></span><span class="line"><span class="cl"><span class="s"> size: 3
</span></span></span><span class="line"><span class="cl"><span class="s"> expose:
</span></span></span><span class="line"><span class="cl"><span class="s"> type: ClusterIP
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span></span></span></code></pre>
</div>
</div>
</div>
<p>Apply:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-48" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-48">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl apply -f cr-main-after.yaml -n psmdb</span></span></code></pre>
</div>
</div>
</div>
<h3 id="15b-add-main-nodes-to-replica-cluster">15b: Add Main nodes to Replica cluster<a class="anchor-link" id="15b-add-main-nodes-to-replica-cluster"></a></h3>
<p>Run in <strong>Terminal 2</strong> (replica cluster).</p>
<p>Copy <code>cr-replica.yaml</code> to <code>cr-replica-after.yaml</code> and add an <strong><code>externalNodes</code></strong> block under<br>
<code>replsets.rs0</code> and under <code>sharding.configsvrReplSet</code>, everything else stays the same.</p>
<p>Create <code>cr-replica-after.yaml</code>:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-49" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-49">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat &gt; cr-replica-after.yaml <span class="s">&lt;&lt; 'EOF'
</span></span></span><span class="line"><span class="cl"><span class="s">apiVersion: psmdb.percona.com/v1
</span></span></span><span class="line"><span class="cl"><span class="s">kind: PerconaServerMongoDB
</span></span></span><span class="line"><span class="cl"><span class="s">metadata:
</span></span></span><span class="line"><span class="cl"><span class="s"> name: replica-cluster
</span></span></span><span class="line"><span class="cl"><span class="s">spec:
</span></span></span><span class="line"><span class="cl"><span class="s"> unmanaged: true
</span></span></span><span class="line"><span class="cl"><span class="s"> crVersion: 1.20.1
</span></span></span><span class="line"><span class="cl"><span class="s"> image: percona/percona-server-mongodb:7.0.14-8-multi
</span></span></span><span class="line"><span class="cl"><span class="s"> updateStrategy: RollingUpdate
</span></span></span><span class="line"><span class="cl"><span class="s"> multiCluster:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> DNSSuffix: svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> upgradeOptions:
</span></span></span><span class="line"><span class="cl"><span class="s"> apply: disabled
</span></span></span><span class="line"><span class="cl"><span class="s"> schedule: "0 2 * * *"
</span></span></span><span class="line"><span class="cl"><span class="s"> secrets:
</span></span></span><span class="line"><span class="cl"><span class="s"> users: my-cluster-name-secrets
</span></span></span><span class="line"><span class="cl"><span class="s"> encryptionKey: my-cluster-name-mongodb-encryption-key
</span></span></span><span class="line"><span class="cl"><span class="s"> ssl: replica-cluster-ssl
</span></span></span><span class="line"><span class="cl"><span class="s"> sslInternal: replica-cluster-ssl-internal
</span></span></span><span class="line"><span class="cl"><span class="s"> replsets:
</span></span></span><span class="line"><span class="cl"><span class="s"> - name: rs0
</span></span></span><span class="line"><span class="cl"><span class="s"> size: 3
</span></span></span><span class="line"><span class="cl"><span class="s"> externalNodes:
</span></span></span><span class="line"><span class="cl"><span class="s"> - host: main-cluster-rs0-0.psmdb.svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> votes: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> priority: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> - host: main-cluster-rs0-1.psmdb.svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> votes: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> priority: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> - host: main-cluster-rs0-2.psmdb.svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> votes: 0
</span></span></span><span class="line"><span class="cl"><span class="s"> priority: 0
</span></span></span><span class="line"><span class="cl"><span class="s"> expose:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> type: ClusterIP
</span></span></span><span class="line"><span class="cl"><span class="s"> volumeSpec:
</span></span></span><span class="line"><span class="cl"><span class="s"> persistentVolumeClaim:
</span></span></span><span class="line"><span class="cl"><span class="s"> resources:
</span></span></span><span class="line"><span class="cl"><span class="s"> requests:
</span></span></span><span class="line"><span class="cl"><span class="s"> storage: 3Gi
</span></span></span><span class="line"><span class="cl"><span class="s"> sharding:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> configsvrReplSet:
</span></span></span><span class="line"><span class="cl"><span class="s"> size: 3
</span></span></span><span class="line"><span class="cl"><span class="s"> externalNodes:
</span></span></span><span class="line"><span class="cl"><span class="s"> - host: main-cluster-cfg-0.psmdb.svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> votes: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> priority: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> - host: main-cluster-cfg-1.psmdb.svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> votes: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> priority: 1
</span></span></span><span class="line"><span class="cl"><span class="s"> - host: main-cluster-cfg-2.psmdb.svc.clusterset.local
</span></span></span><span class="line"><span class="cl"><span class="s"> votes: 0
</span></span></span><span class="line"><span class="cl"><span class="s"> priority: 0
</span></span></span><span class="line"><span class="cl"><span class="s"> expose:
</span></span></span><span class="line"><span class="cl"><span class="s"> enabled: true
</span></span></span><span class="line"><span class="cl"><span class="s"> type: ClusterIP
</span></span></span><span class="line"><span class="cl"><span class="s"> volumeSpec:
</span></span></span><span class="line"><span class="cl"><span class="s"> persistentVolumeClaim:
</span></span></span><span class="line"><span class="cl"><span class="s"> resources:
</span></span></span><span class="line"><span class="cl"><span class="s"> requests:
</span></span></span><span class="line"><span class="cl"><span class="s"> storage: 3Gi
</span></span></span><span class="line"><span class="cl"><span class="s"> mongos:
</span></span></span><span class="line"><span class="cl"><span class="s"> size: 3
</span></span></span><span class="line"><span class="cl"><span class="s"> expose:
</span></span></span><span class="line"><span class="cl"><span class="s"> type: ClusterIP
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span></span></span></code></pre>
</div>
</div>
</div>
<p>Apply:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-50" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-50">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl apply -f cr-replica-after.yaml -n psmdb</span></span></code></pre>
</div>
</div>
</div>
<blockquote>
<p><strong>After interconnect:</strong> Pods may restart on both clusters while MongoDB reconfigures<br>
the replica sets, brief <code>CrashLoopBackOff</code> on replica is normal. Wait until all<br>
pods are <code>1/1 Running</code> before continuing to Step 16.</p>
</blockquote>
<h2 id="step-16-verify-cross-cluster-replication">Step 16: Verify cross-cluster replication<a class="anchor-link" id="step-16-verify-cross-cluster-replication"></a></h2>
<p>Run in <strong>Terminal 1</strong> (main cluster).</p>
<p>Get the clusterAdmin password:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-51" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-51">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get secret my-cluster-name-secrets <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -n psmdb <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> -o <span class="nv">jsonpath</span><span class="o">=</span><span class="s2">"{.data.MONGODB_CLUSTER_ADMIN_PASSWORD}"</span> <span class="p">|</span> base64 --decode</span></span></code></pre>
</div>
</div>
</div>
<p>Connect to the main cluster config server:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-52" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-52">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl <span class="nb">exec</span> -it main-cluster-cfg-0 -n psmdb -- /bin/bash</span></span></code></pre>
</div>
</div>
</div>
<p>Inside the pod:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-53" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-53">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mongosh admin -u clusterAdmin -p </span></span></code></pre>
</div>
</div>
</div>
<p>Check replica set members:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">javascript</span><button class="code-block__copy" type="button" data-copy-target="codeblock-54" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-54">
<div class="highlight">
<pre class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">rs</span><span class="p">.</span><span class="nx">status</span><span class="p">().</span><span class="nx">members</span></span></span></code></pre>
</div>
</div>
</div>
<p>Expected output, 6 members total, all using <code>svc.clusterset.local</code> DNS names:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">javascript</span><button class="code-block__copy" type="button" data-copy-target="codeblock-55" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-55">
<div class="highlight">
<pre class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">cfg</span> <span class="p">[</span><span class="nx">direct</span><span class="o">:</span> <span class="nx">primary</span><span class="p">]</span> <span class="nx">admin</span><span class="o">&gt;</span> <span class="nx">rs</span><span class="p">.</span><span class="nx">status</span><span class="p">().</span><span class="nx">members</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">_id</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'main-cluster-cfg-0.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">health</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">state</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">stateStr</span><span class="o">:</span> <span class="s1">'PRIMARY'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">uptime</span><span class="o">:</span> <span class="mi">17202</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceHost</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceId</span><span class="o">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">infoMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">electionTime</span><span class="o">:</span> <span class="nx">Timestamp</span><span class="p">({</span> <span class="nx">t</span><span class="o">:</span> <span class="mi">1780921358</span><span class="p">,</span> <span class="nx">i</span><span class="o">:</span> <span class="mi">2</span> <span class="p">}),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">electionDate</span><span class="o">:</span> <span class="nx">ISODate</span><span class="p">(</span><span class="s1">'2026-06-08T12:22:38.000Z'</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configVersion</span><span class="o">:</span> <span class="mi">14</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configTerm</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">self</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">lastHeartbeatMessage</span><span class="o">:</span> <span class="s1">''</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">_id</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'main-cluster-cfg-1.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">health</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">state</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">stateStr</span><span class="o">:</span> <span class="s1">'SECONDARY'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">uptime</span><span class="o">:</span> <span class="mi">17034</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pingMs</span><span class="o">:</span> <span class="nx">Long</span><span class="p">(</span><span class="s1">'0'</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">lastHeartbeatMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceHost</span><span class="o">:</span> <span class="s1">'main-cluster-cfg-0.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceId</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">infoMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configVersion</span><span class="o">:</span> <span class="mi">14</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configTerm</span><span class="o">:</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">_id</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'main-cluster-cfg-2.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">health</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">state</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">stateStr</span><span class="o">:</span> <span class="s1">'SECONDARY'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">uptime</span><span class="o">:</span> <span class="mi">16861</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pingMs</span><span class="o">:</span> <span class="nx">Long</span><span class="p">(</span><span class="s1">'0'</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">lastHeartbeatMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceHost</span><span class="o">:</span> <span class="s1">'main-cluster-cfg-1.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceId</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">infoMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configVersion</span><span class="o">:</span> <span class="mi">14</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configTerm</span><span class="o">:</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">_id</span><span class="o">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'replica-cluster-cfg-0.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">health</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">state</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">stateStr</span><span class="o">:</span> <span class="s1">'SECONDARY'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">uptime</span><span class="o">:</span> <span class="mi">3214</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pingMs</span><span class="o">:</span> <span class="nx">Long</span><span class="p">(</span><span class="s1">'0'</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">lastHeartbeatMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceHost</span><span class="o">:</span> <span class="s1">'main-cluster-cfg-0.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceId</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">infoMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configVersion</span><span class="o">:</span> <span class="mi">14</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configTerm</span><span class="o">:</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">_id</span><span class="o">:</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'replica-cluster-cfg-1.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">health</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">state</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">stateStr</span><span class="o">:</span> <span class="s1">'SECONDARY'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">uptime</span><span class="o">:</span> <span class="mi">3181</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pingMs</span><span class="o">:</span> <span class="nx">Long</span><span class="p">(</span><span class="s1">'0'</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">lastHeartbeatMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceHost</span><span class="o">:</span> <span class="s1">'replica-cluster-cfg-0.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceId</span><span class="o">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">infoMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configVersion</span><span class="o">:</span> <span class="mi">14</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configTerm</span><span class="o">:</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">_id</span><span class="o">:</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'replica-cluster-cfg-2.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">health</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">state</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">stateStr</span><span class="o">:</span> <span class="s1">'SECONDARY'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">uptime</span><span class="o">:</span> <span class="mi">3164</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pingMs</span><span class="o">:</span> <span class="nx">Long</span><span class="p">(</span><span class="s1">'0'</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">lastHeartbeatMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceHost</span><span class="o">:</span> <span class="s1">'main-cluster-cfg-2.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceId</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">infoMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configVersion</span><span class="o">:</span> <span class="mi">14</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configTerm</span><span class="o">:</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span></span></span></code></pre>
</div>
</div>
</div>
<p>If all 6 members appear with <code>health: 1</code>, cross-cluster replication is working.</p>
<h2 id="step-17-test-the-switchover-process">Step 17: Test the switchover process<a class="anchor-link" id="step-17-test-the-switchover-process"></a></h2>
<p>In a multi-cluster deployment, <strong>only one Operator should actively manage the replica<br>
set at a time</strong>, otherwise both sites could try to reconfigure MongoDB and cause<br>
split-brain.</p>
<p>Until now, the <strong>main</strong> Operator was in charge (<code>unmanaged</code> not set, so managed by<br>
default). The <strong>replica</strong> Operator only kept pods running (<code>unmanaged: true</code>) and<br>
did not drive failover or replica-set changes.</p>
<p>This step simulates a site failover in two moves:</p>
<ol>
<li><strong>Main &rarr; unmanaged</strong>: main Operator stops managing the replica set.</li>
<li><strong>Replica &rarr; managed</strong>: replica Operator takes over and can elect a new PRIMARY.</li>
</ol>
<p>Apply both changes below, then verify MongoDB elects a new PRIMARY on the replica side.</p>
<p><strong>Terminal 1 (main cluster)</strong>, release Operator control on main:</p>
<p>Edit <code>cr-main-after.yaml</code> under <code>spec:</code>, add <code>unmanaged: true</code> and change<br>
<code>updateStrategy</code> from <code>SmartUpdate</code> to <code>RollingUpdate</code> (SmartUpdate requires a<br>
managed cluster):</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">yaml</span><button class="code-block__copy" type="button" data-copy-target="codeblock-56" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-56">
<div class="highlight">
<pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w"> </span><span class="nt">unmanaged</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">updateStrategy</span><span class="p">:</span><span class="w"> </span><span class="l">RollingUpdate</span></span></span></code></pre>
</div>
</div>
</div>
<p>Apply:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-57" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-57">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl apply -f cr-main-after.yaml -n psmdb</span></span></code></pre>
</div>
</div>
</div>
<p><strong>Terminal 2 (replica cluster)</strong>, give Operator control on replica:</p>
<p>Edit <code>cr-replica-after.yaml</code> under <code>spec:</code>, change <code>unmanaged: true</code> to<br>
<code>unmanaged: false</code> so the replica Operator can manage failover and replica-set<br>
reconfiguration:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">yaml</span><button class="code-block__copy" type="button" data-copy-target="codeblock-58" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-58">
<div class="highlight">
<pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w"> </span><span class="nt">unmanaged</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span></span></span></code></pre>
</div>
</div>
</div>
<p>Apply:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-59" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-59">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl apply -f cr-replica-after.yaml -n psmdb</span></span></code></pre>
</div>
</div>
</div>
<p>Verify a new PRIMARY was elected on the replica side (<strong>Terminal 2</strong>):</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-60" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-60">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl <span class="nb">exec</span> -it replica-cluster-cfg-0 -n psmdb -- /bin/bash</span></span></code></pre>
</div>
</div>
</div>
<p>Inside the pod:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-61" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-61">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mongosh admin -u clusterAdmin -p </span></span></code></pre>
</div>
</div>
</div>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">javascript</span><button class="code-block__copy" type="button" data-copy-target="codeblock-62" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-62">
<div class="highlight">
<pre class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">rs</span><span class="p">.</span><span class="nx">status</span><span class="p">().</span><span class="nx">members</span></span></span></code></pre>
</div>
</div>
</div>
<p>Expected: <code>replica-cluster-cfg-0</code> is PRIMARY, main-side members are SECONDARY:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">javascript</span><button class="code-block__copy" type="button" data-copy-target="codeblock-63" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-63">
<div class="highlight">
<pre class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">_id</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'main-cluster-cfg-0.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">health</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">state</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">stateStr</span><span class="o">:</span> <span class="s1">'SECONDARY'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">uptime</span><span class="o">:</span> <span class="mi">19106</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceHost</span><span class="o">:</span> <span class="s1">'replica-cluster-cfg-1.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceId</span><span class="o">:</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">infoMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configVersion</span><span class="o">:</span> <span class="mi">20</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configTerm</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">self</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">lastHeartbeatMessage</span><span class="o">:</span> <span class="s1">''</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">_id</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'main-cluster-cfg-1.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">health</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">state</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">stateStr</span><span class="o">:</span> <span class="s1">'SECONDARY'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">uptime</span><span class="o">:</span> <span class="mi">18938</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pingMs</span><span class="o">:</span> <span class="nx">Long</span><span class="p">(</span><span class="s1">'0'</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">lastHeartbeatMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceHost</span><span class="o">:</span> <span class="s1">'main-cluster-cfg-0.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceId</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">infoMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configVersion</span><span class="o">:</span> <span class="mi">20</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configTerm</span><span class="o">:</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">_id</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'main-cluster-cfg-2.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">health</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">state</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">stateStr</span><span class="o">:</span> <span class="s1">'SECONDARY'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">uptime</span><span class="o">:</span> <span class="mi">18765</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pingMs</span><span class="o">:</span> <span class="nx">Long</span><span class="p">(</span><span class="s1">'0'</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">lastHeartbeatMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceHost</span><span class="o">:</span> <span class="s1">'main-cluster-cfg-1.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceId</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">infoMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configVersion</span><span class="o">:</span> <span class="mi">20</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configTerm</span><span class="o">:</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">_id</span><span class="o">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'replica-cluster-cfg-0.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">health</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">state</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">stateStr</span><span class="o">:</span> <span class="s1">'PRIMARY'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">uptime</span><span class="o">:</span> <span class="mi">5118</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pingMs</span><span class="o">:</span> <span class="nx">Long</span><span class="p">(</span><span class="s1">'0'</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">lastHeartbeatMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceHost</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceId</span><span class="o">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">infoMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">electionTime</span><span class="o">:</span> <span class="nx">Timestamp</span><span class="p">({</span> <span class="nx">t</span><span class="o">:</span> <span class="mi">1780940264</span><span class="p">,</span> <span class="nx">i</span><span class="o">:</span> <span class="mi">1</span> <span class="p">}),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">electionDate</span><span class="o">:</span> <span class="nx">ISODate</span><span class="p">(</span><span class="s1">'2026-06-08T17:37:44.000Z'</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configVersion</span><span class="o">:</span> <span class="mi">20</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configTerm</span><span class="o">:</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">_id</span><span class="o">:</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'replica-cluster-cfg-1.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">health</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">state</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">stateStr</span><span class="o">:</span> <span class="s1">'SECONDARY'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">uptime</span><span class="o">:</span> <span class="mi">5085</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pingMs</span><span class="o">:</span> <span class="nx">Long</span><span class="p">(</span><span class="s1">'0'</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">lastHeartbeatMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceHost</span><span class="o">:</span> <span class="s1">'replica-cluster-cfg-0.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceId</span><span class="o">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">infoMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configVersion</span><span class="o">:</span> <span class="mi">20</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configTerm</span><span class="o">:</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">_id</span><span class="o">:</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'replica-cluster-cfg-2.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">health</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">state</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">stateStr</span><span class="o">:</span> <span class="s1">'SECONDARY'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">uptime</span><span class="o">:</span> <span class="mi">5068</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pingMs</span><span class="o">:</span> <span class="nx">Long</span><span class="p">(</span><span class="s1">'0'</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">lastHeartbeatMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceHost</span><span class="o">:</span> <span class="s1">'replica-cluster-cfg-0.psmdb.svc.clusterset.local:27017'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">syncSourceId</span><span class="o">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">infoMessage</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configVersion</span><span class="o">:</span> <span class="mi">20</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">configTerm</span><span class="o">:</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span></span></span></code></pre>
</div>
</div>
</div>
<h2 id="step-18-cleanup">Step 18: Cleanup<a class="anchor-link" id="step-18-cleanup"></a></h2>
<p>To remove the GKE clusters when you are done:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">bash</span><button class="code-block__copy" type="button" data-copy-target="codeblock-64" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-64">
<div class="highlight">
<pre class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcloud container clusters delete main-cluster <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --zone us-central1-a <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --quiet
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">gcloud container clusters delete replica-cluster <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --zone us-central1-a <span class="se">
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --quiet</span></span></code></pre>
</div>
</div>
</div>
<h2 id="references">References<a class="anchor-link" id="references"></a></h2>
<ul>
<li>
<p><a href="https://www.percona.com/blog/deploying-percona-operator-for-mongodb-across-gke-clusters-with-mcs/" target="_blank" rel="noopener noreferrer">Original blog post by Ivan Groenewold</a></p>
</li>
<li>
<p><a href="https://docs.percona.com/percona-operator-for-mongodb/replication-mcs.html" target="_blank" rel="noopener noreferrer">Percona Operator for MongoDB, Multi-Cluster Services</a></p>
</li>
<li>
<p><a href="https://cloud.google.com/kubernetes-engine/docs/concepts/multi-cluster-services" target="_blank" rel="noopener noreferrer">GKE Multi-Cluster Services overview</a></p>
</li>
<li>
<p><a href="https://docs.percona.com/percona-operator-for-mongodb/replication-mcs-gke.html" target="_blank" rel="noopener noreferrer">GKE MCS setup, Percona docs</a></p>
</li>
<li>
<p><a href="https://www.mongodb.com/docs/manual/core/replica-set-elections/" target="_blank" rel="noopener noreferrer">MongoDB replica set elections</a></p>
</li>
<li>
<p><a href="https://github.com/kubernetes/enhancements/blob/master/keps/sig-multicluster/1645-multi-cluster-services-api/README.md" target="_blank" rel="noopener noreferrer">Kubernetes MCS API KEP-1645</a></p>
</li>
</ul>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/06/12/multi-cluster-mongodb-percona-operator/">Guide Multi-Cluster MongoDB on GKE with MCS, Percona Operator</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB shortens maintenance period from 5 to 3 years</title>
      <link rel="alternate" type="text/html" href="https://www.fromdual.com/blog/mariadb-shortens-maintenance-period-from-5-to-3-years/" />
      <id>https://www.fromdual.com/blog/mariadb-shortens-maintenance-period-from-5-to-3-years/</id>
      <updated>2026-06-11T07:34:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>Somehow this news slipped past me: MariaDB has shortened the support period for the long-term releases of the MariaDB Community Server from 5 to 3 years. OK, I guess that’s not really surprising — I’ve been offline for a good month…<br />
MariaDB Server LTS Release Support Periods</p>
<p> Release<br />
 GA date<br />
 EoL date<br />
 Duration</p>
<p> 12.3<br />
 28 May 2026<br />
 Jun 2029<br />
 3 years</p>
<p> 11.8<br />
 4 Jun 2025<br />
 4 Jun 2028<br />
 3 years</p>
<p> 11.4<br />
 29 May 2024<br />
 29 May 2029<br />
 5 years</p>
<p> 10.11<br />
 16 Feb 2023<br />
 16 Feb 2028<br />
 5 years</p>
<p> 10.6<br />
 6 Jul 2021<br />
 6 Jul 2026<br />
 5 years</p>
<p> 10.5<br />
 24 Jun 2020<br />
 24 Jun 2025<br />
 5 years</p>
<p> 10.4<br />
 18 Jun 2019<br />
 18 Jun 2024<br />
 5 years</p>
<p> 10.3<br />
 25 May 2018<br />
 25 May 2023<br />
 5 years</p>
<p> 10.2<br />
 23 May 2017<br />
 23 May 2022<br />
 5 years</p>
<p> 10.1<br />
 17 Oct 2015<br />
 17 Oct 2020<br />
 5 years</p>
<p> 10.0<br />
 31 Mar 2014<br />
 31 Mar 2019<br />
 5 years</p>
<p>Source: MariaDB Server long-term release maintenance periods<br />
I am curious to see how all the distributions will handle this. They have significantly longer support periods, after all.<br />
And what about the competitors — the other databases?<br />
Debian</p>
<p>Debian Long Term Support (LTS) is a project to extend the lifetime of all Debian stable releases to (at least) 5 years.<br />
Source: Debian Long Term Support</p>
<p> Version<br />
 Name<br />
 Release<br />
 ext-LTS<br />
 EoL<br />
 Duration</p>
<p> Debian 13<br />
 trixie<br />
 2025-08-09<br />
 2030-07-01<br />
 2035-06-30<br />
 5 / 10 years</p>
<p> Debian 12<br />
 bookworm<br />
 2023-06-10<br />
 2028-07-01<br />
 2033-06-30<br />
 5 / 10 years</p>
<p> Debian 11<br />
 bullseye<br />
 2021-08-14<br />
 2026-09-01<br />
 2031-06-30<br />
 5 / 10 years</p>
<p> Debian 10<br />
 buster<br />
 2019-06-06<br />
 2024-07-01<br />
 2029-06-30<br />
 5 / 10 years</p>
<p> Debian 9<br />
 stretch<br />
 2017-06-17<br />
 2022-07-01<br />
 2027-06-30<br />
 5 / 10 years</p>
<p> Debian 8<br />
 jessie<br />
 2015-04-26<br />
 2020-07-01<br />
 2025-06-30<br />
 5 / 10 years</p>
<p> Debian 7<br />
 wheezy<br />
 2013-05-04<br />
 2018-06-01<br />
 2020-06-30<br />
 5 / 7 years</p>
<p>Source: Extended Long Term Support<br />
Ubuntu</p>
<p> Version<br />
 Name<br />
 Release<br />
 End of Support<br />
 EoL<br />
 Duration</p>
<p> Ubuntu 26.04 LTS<br />
 Resolute Raccoon<br />
 23. April 2026<br />
 May 2031<br />
 April 2041<br />
 5 / 15 years</p>
<p> Ubuntu 24.04 LTS<br />
 Noble Numbat<br />
 25. April 2024<br />
 June 2029<br />
 April 2039<br />
 5 / 15 years</p>
<p> Ubuntu 22.04 LTS<br />
 Jammy Jellyfish<br />
 21. April 2022<br />
 June 2027<br />
 April 2037<br />
 5 / 15 years</p>
<p> Ubuntu 20.04 LTS<br />
 Focal Fossa<br />
 23. April 2020<br />
 May 2025<br />
 April 2035<br />
 5 / 15 years</p>
<p> Ubuntu 18.04 LTS<br />
 Bionic Beaver<br />
 26. April 2018<br />
 June 2023<br />
 April 2033<br />
 5 / 15 years</p>
<p> Ubuntu 16.04 LTS<br />
 Xenial Xerus<br />
 21. April 2016<br />
 April 2021<br />
 April 2031<br />
 5 / 15 years</p>
<p> Ubuntu 14.04 LTS<br />
 Trusty Tahr<br />
 17. April 2014<br />
 April 2019<br />
 April 2029<br />
 5 / 15 years</p>
<p>Source: List of releases<br />
Rocky Linux</p>
<p> Release<br />
 Codename<br />
 Release Date<br />
 Active Support End<br />
 End of Life<br />
 Duration</p>
<p> Rocky Linux 10<br />
 Red Quartz<br />
 June 11, 2025<br />
 May 31, 2030<br />
 May 31, 2035<br />
 5 / 10 years</p>
<p> Rocky Linux 9<br />
 Blue Onyx<br />
 July 14, 2022<br />
 May 31, 2027<br />
 May 31, 2032<br />
 5 / 10 years</p>
<p> Rocky Linux 8<br />
 Green Obsidian<br />
 May 1, 2021<br />
 May 31, 2024<br />
 May 31, 2029<br />
 3 / 8 years</p>
<p>Source: Rocky Linux Release and Version Guide<br />
Oracle / MySQL Releases</p>
<p> Release<br />
 GA Date<br />
 Premier Support End<br />
 Extended Support End<br />
 Duration</p>
<p> MySQL 9.7<br />
 Apr 2026<br />
 Apr 2031<br />
 Apr 2034<br />
 5 / 8 years</p>
<p> MySQL 8.4<br />
 Apr 2024<br />
 Apr 2029<br />
 Apr 2032<br />
 5 / 8 years</p>
<p> MySQL 8.0<br />
 Apr 2018<br />
 Apr 2025<br />
 Apr 2026<br />
 7 years / 8 years</p>
<p> MySQL 5.7<br />
 Oct 2015<br />
 Oct 2020<br />
 Oct 2023<br />
 5 years / 8 years</p>
<p> MySQL 5.6<br />
 Feb 2013<br />
 Feb 2018<br />
 Feb 2021<br />
 5 years / 8 years</p>
<p> MySQL 5.5<br />
 Dec 2010<br />
 Dec 2015<br />
 Dec 2018<br />
 5 years / 8 years</p>
<p> MySQL 5.1<br />
 Dec 2008<br />
 Dec 2013<br />
 Not Available<br />
 5 years</p>
<p> MySQL 5.0<br />
 Oct 2005<br />
 Dec 2011<br />
 Not Available<br />
 6 years</p>
<p>Source: Oracle Lifetime Support Policy<br />
Percona<br />
Percona Distribution for PostgreSQL (PDPG) und Percona Server for MySQL (PS): At least 5 years, if I am interpreting the support matrix correctly…<br />
Source: Percona Release Lifecycle Overview<br />
OurSQL / VillageSQL<br />
No finished software is available yet, and thus no support policies, as far as I know. Is that even planned at all?<br />
Source: OurSQL und VillageSQL<br />
PostgreSQL</p>
<p> Version<br />
 First Release<br />
 Final Release<br />
 Duration</p>
<p> 18<br />
 September 25, 2025<br />
 November 14, 2030<br />
 5 years</p>
<p> 17<br />
 September 26, 2024<br />
 November 8, 2029<br />
 5 years</p>
<p> 16<br />
 September 14, 2023<br />
 November 9, 2028<br />
 5 years</p>
<p> 15<br />
 October 13, 2022<br />
 November 11, 2027<br />
 5 years</p>
<p> 14<br />
 September 30, 2021<br />
 November 12, 2026<br />
 5 years</p>
<p> 13<br />
 September 24, 2020<br />
 November 13, 2025<br />
 5 years</p>
<p> 12<br />
 October 3, 2019<br />
 November 21, 2024<br />
 5 years</p>
<p> 11<br />
 October 18, 2018<br />
 November 9, 2023<br />
 5 years</p>
<p> 10<br />
 October 5, 2017<br />
 November 10, 2022<br />
 5 years</p>
<p>Source: Versioning Policy<br />
Further sources</p>
<p>MariaDB 10.6 Changes &#038; Improvements<br />
MariaDB 10.6 is a long-term maintenance stable version. The first stable release was in July 2021, and it will be maintained until July 2026.<br />
MariaDB 10.11 Changes &#038; Improvements<br />
MariaDB 10.11 is a long-term maintenance release series, maintained until February 2028.<br />
MariaDB 11.4 Changes &#038; Improvements<br />
MariaDB 11.4 is a current long-term series, maintained until May 2029.<br />
MariaDB 11.8 Changes &#038; Improvements<br />
MariaDB 11.8 is a long-term release, maintained until June 2028.<br />
MariaDB 12.3 Changes &#038; Improvements<br />
MariaDB 12.3 is a long term release, maintained until June 2029.</p>
<p>The post <a rel="nofollow" href="https://www.fromdual.com/blog/mariadb-shortens-maintenance-period-from-5-to-3-years/">MariaDB shortens maintenance period from 5 to 3 years</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Somehow this news slipped past me: MariaDB has shortened the support period for the long-term releases of the MariaDB Community Server from 5 to 3 years. OK, I guess that&rsquo;s not really surprising &mdash; I&rsquo;ve been offline for a good month&hellip;</p>
<h2 id="mariadb-server-lts-release-support-periods">MariaDB Server LTS Release Support Periods<a class="anchor-link" id="mariadb-server-lts-release-support-periods"></a></h2>
<table>
<thead>
<tr>
<th>Release</th>
<th>GA date</th>
<th>EoL date</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>12.3</td>
<td>28 May 2026</td>
<td>Jun 2029</td>
<td><strong>3 years</strong></td>
</tr>
<tr>
<td>11.8</td>
<td>4 Jun 2025</td>
<td>4 Jun 2028</td>
<td><strong>3 years</strong></td>
</tr>
<tr>
<td>11.4</td>
<td>29 May 2024</td>
<td>29 May 2029</td>
<td>5 years</td>
</tr>
<tr>
<td>10.11</td>
<td>16 Feb 2023</td>
<td>16 Feb 2028</td>
<td>5 years</td>
</tr>
<tr>
<td>10.6</td>
<td>6 Jul 2021</td>
<td>6 Jul 2026</td>
<td>5 years</td>
</tr>
<tr>
<td>10.5</td>
<td>24 Jun 2020</td>
<td>24 Jun 2025</td>
<td>5 years</td>
</tr>
<tr>
<td>10.4</td>
<td>18 Jun 2019</td>
<td>18 Jun 2024</td>
<td>5 years</td>
</tr>
<tr>
<td>10.3</td>
<td>25 May 2018</td>
<td>25 May 2023</td>
<td>5 years</td>
</tr>
<tr>
<td>10.2</td>
<td>23 May 2017</td>
<td>23 May 2022</td>
<td>5 years</td>
</tr>
<tr>
<td>10.1</td>
<td>17 Oct 2015</td>
<td>17 Oct 2020</td>
<td>5 years</td>
</tr>
<tr>
<td>10.0</td>
<td>31 Mar 2014</td>
<td>31 Mar 2019</td>
<td>5 years</td>
</tr>
</tbody>
</table>
<p>Source: <a href="https://mariadb.org/about/#maintenance-policy" target="_blank" rel="noopener">MariaDB Server long-term release maintenance periods</a></p>
<p>I am curious to see how all the distributions will handle this. They have significantly longer support periods, after all.</p>
<p>And what about the competitors &mdash; the other databases?</p>
<h2 id="debian">Debian<a class="anchor-link" id="debian"></a></h2>
<blockquote>
<p>Debian Long Term Support (LTS) is a project to extend the lifetime of all Debian stable releases to (at least) 5 years.</p>
</blockquote>
<p>Source: <a href="https://wiki.debian.org/LTS" target="_blank" rel="noopener">Debian Long Term Support</a></p>
<table>
<thead>
<tr>
<th>Version</th>
<th>Name</th>
<th>Release</th>
<th>ext-LTS</th>
<th>EoL</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>Debian 13</td>
<td>trixie</td>
<td>2025-08-09</td>
<td>2030-07-01</td>
<td>2035-06-30</td>
<td>5 / 10 years</td>
</tr>
<tr>
<td>Debian 12</td>
<td>bookworm</td>
<td>2023-06-10</td>
<td>2028-07-01</td>
<td>2033-06-30</td>
<td>5 / 10 years</td>
</tr>
<tr>
<td>Debian 11</td>
<td>bullseye</td>
<td>2021-08-14</td>
<td>2026-09-01</td>
<td>2031-06-30</td>
<td>5 / 10 years</td>
</tr>
<tr>
<td>Debian 10</td>
<td>buster</td>
<td>2019-06-06</td>
<td>2024-07-01</td>
<td>2029-06-30</td>
<td>5 / 10 years</td>
</tr>
<tr>
<td>Debian 9</td>
<td>stretch</td>
<td>2017-06-17</td>
<td>2022-07-01</td>
<td>2027-06-30</td>
<td>5 / 10 years</td>
</tr>
<tr>
<td>Debian 8</td>
<td>jessie</td>
<td>2015-04-26</td>
<td>2020-07-01</td>
<td>2025-06-30</td>
<td>5 / 10 years</td>
</tr>
<tr>
<td>Debian 7</td>
<td>wheezy</td>
<td>2013-05-04</td>
<td>2018-06-01</td>
<td>2020-06-30</td>
<td>5 / 7 years</td>
</tr>
</tbody>
</table>
<p>Source: <a href="https://wiki.debian.org/LTS/Extended" target="_blank" rel="noopener">Extended Long Term Support</a></p>
<h2 id="ubuntu">Ubuntu<a class="anchor-link" id="ubuntu"></a></h2>
<table>
<thead>
<tr>
<th>Version</th>
<th>Name</th>
<th>Release</th>
<th>End of Support</th>
<th>EoL</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ubuntu 26.04 LTS</td>
<td>Resolute Raccoon</td>
<td>23. April 2026</td>
<td>May 2031</td>
<td>April 2041</td>
<td>5 / 15 years</td>
</tr>
<tr>
<td>Ubuntu 24.04 LTS</td>
<td>Noble Numbat</td>
<td>25. April 2024</td>
<td>June 2029</td>
<td>April 2039</td>
<td>5 / 15 years</td>
</tr>
<tr>
<td>Ubuntu 22.04 LTS</td>
<td>Jammy Jellyfish</td>
<td>21. April 2022</td>
<td>June 2027</td>
<td>April 2037</td>
<td>5 / 15 years</td>
</tr>
<tr>
<td>Ubuntu 20.04 LTS</td>
<td>Focal Fossa</td>
<td>23. April 2020</td>
<td>May 2025</td>
<td>April 2035</td>
<td>5 / 15 years</td>
</tr>
<tr>
<td>Ubuntu 18.04 LTS</td>
<td>Bionic Beaver</td>
<td>26. April 2018</td>
<td>June 2023</td>
<td>April 2033</td>
<td>5 / 15 years</td>
</tr>
<tr>
<td>Ubuntu 16.04 LTS</td>
<td>Xenial Xerus</td>
<td>21. April 2016</td>
<td>April 2021</td>
<td>April 2031</td>
<td>5 / 15 years</td>
</tr>
<tr>
<td>Ubuntu 14.04 LTS</td>
<td>Trusty Tahr</td>
<td>17. April 2014</td>
<td>April 2019</td>
<td>April 2029</td>
<td>5 / 15 years</td>
</tr>
</tbody>
</table>
<p>Source: <a href="https://documentation.ubuntu.com/project/release-team/list-of-releases/" target="_blank" rel="noopener">List of releases</a></p>
<h2 id="rocky-linux">Rocky Linux<a class="anchor-link" id="rocky-linux"></a></h2>
<table>
<thead>
<tr>
<th>Release</th>
<th>Codename</th>
<th>Release Date</th>
<th>Active Support End</th>
<th>End of Life</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>Rocky Linux 10</td>
<td>Red Quartz</td>
<td>June 11, 2025</td>
<td>May 31, 2030</td>
<td>May 31, 2035</td>
<td>5 / 10 years</td>
</tr>
<tr>
<td>Rocky Linux 9</td>
<td>Blue Onyx</td>
<td>July 14, 2022</td>
<td>May 31, 2027</td>
<td>May 31, 2032</td>
<td>5 / 10 years</td>
</tr>
<tr>
<td>Rocky Linux 8</td>
<td>Green Obsidian</td>
<td>May 1, 2021</td>
<td>May 31, 2024</td>
<td>May 31, 2029</td>
<td>3 / 8 years</td>
</tr>
</tbody>
</table>
<p>Source: <a href="https://wiki.rockylinux.org/rocky/version/#current-supported-releases" target="_blank" rel="noopener">Rocky Linux Release and Version Guide</a></p>
<h2 id="oracle--mysql-releases">Oracle / MySQL Releases<a class="anchor-link" id="oracle-mysql-releases"></a></h2>
<table>
<thead>
<tr>
<th>Release</th>
<th>GA Date</th>
<th>Premier Support End</th>
<th>Extended Support End</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>MySQL 9.7</td>
<td>Apr 2026</td>
<td>Apr 2031</td>
<td>Apr 2034</td>
<td>5 / 8 years</td>
</tr>
<tr>
<td>MySQL 8.4</td>
<td>Apr 2024</td>
<td>Apr 2029</td>
<td>Apr 2032</td>
<td>5 / 8 years</td>
</tr>
<tr>
<td>MySQL 8.0</td>
<td>Apr 2018</td>
<td>Apr 2025</td>
<td>Apr 2026</td>
<td>7 years / 8 years</td>
</tr>
<tr>
<td>MySQL 5.7</td>
<td>Oct 2015</td>
<td>Oct 2020</td>
<td>Oct 2023</td>
<td>5 years / 8 years</td>
</tr>
<tr>
<td>MySQL 5.6</td>
<td>Feb 2013</td>
<td>Feb 2018</td>
<td>Feb 2021</td>
<td>5 years / 8 years</td>
</tr>
<tr>
<td>MySQL 5.5</td>
<td>Dec 2010</td>
<td>Dec 2015</td>
<td>Dec 2018</td>
<td>5 years / 8 years</td>
</tr>
<tr>
<td>MySQL 5.1</td>
<td>Dec 2008</td>
<td>Dec 2013</td>
<td>Not Available</td>
<td>5 years</td>
</tr>
<tr>
<td>MySQL 5.0</td>
<td>Oct 2005</td>
<td>Dec 2011</td>
<td>Not Available</td>
<td>6 years</td>
</tr>
</tbody>
</table>
<p>Source: <a href="https://www.oracle.com/us/support/library/lifetime-support-technology-069183.pdf" target="_blank" rel="noopener">Oracle Lifetime Support Policy</a></p>
<h2 id="percona">Percona<a class="anchor-link" id="percona"></a></h2>
<p>Percona Distribution for PostgreSQL (PDPG) und Percona Server for MySQL (PS): At least 5 years, if I am interpreting the support matrix correctly&hellip;</p>
<p>Source: <a href="https://www.percona.com/release-lifecycle-overview/" target="_blank" rel="noopener">Percona Release Lifecycle Overview</a></p>
<h2 id="oursql--villagesql">OurSQL / VillageSQL<a class="anchor-link" id="oursql-villagesql"></a></h2>
<p>No finished software is available yet, and thus no support policies, as far as I know. Is that even planned at all?</p>
<p>Source: <a href="https://oursqlfoundation.org/" target="_blank" rel="noopener">OurSQL</a> und <a href="https://villagesql.com/" target="_blank" rel="noopener">VillageSQL</a></p>
<h2 id="postgresql">PostgreSQL<a class="anchor-link" id="postgresql"></a></h2>
<table>
<thead>
<tr>
<th>Version</th>
<th>First Release</th>
<th>Final Release</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>18</td>
<td>September 25, 2025</td>
<td>November 14, 2030</td>
<td>5 years</td>
</tr>
<tr>
<td>17</td>
<td>September 26, 2024</td>
<td>November 8, 2029</td>
<td>5 years</td>
</tr>
<tr>
<td>16</td>
<td>September 14, 2023</td>
<td>November 9, 2028</td>
<td>5 years</td>
</tr>
<tr>
<td>15</td>
<td>October 13, 2022</td>
<td>November 11, 2027</td>
<td>5 years</td>
</tr>
<tr>
<td>14</td>
<td>September 30, 2021</td>
<td>November 12, 2026</td>
<td>5 years</td>
</tr>
<tr>
<td>13</td>
<td>September 24, 2020</td>
<td>November 13, 2025</td>
<td>5 years</td>
</tr>
<tr>
<td>12</td>
<td>October 3, 2019</td>
<td>November 21, 2024</td>
<td>5 years</td>
</tr>
<tr>
<td>11</td>
<td>October 18, 2018</td>
<td>November 9, 2023</td>
<td>5 years</td>
</tr>
<tr>
<td>10</td>
<td>October 5, 2017</td>
<td>November 10, 2022</td>
<td>5 years</td>
</tr>
</tbody>
</table>
<p>Source: <a href="https://www.postgresql.org/support/versioning/" target="_blank" rel="noopener">Versioning Policy</a></p>
<h2 id="further-sources">Further sources<a class="anchor-link" id="further-sources"></a></h2>
<ul>
<li><a href="https://mariadb.com/docs/release-notes/community-server/10.6/what-is-mariadb-106" target="_blank" rel="noopener">MariaDB 10.6 Changes &amp; Improvements</a><br>
<em>MariaDB 10.6 is a long-term maintenance stable version. The first stable release was in July 2021, and it will be maintained until July 2026.</em></li>
<li><a href="https://mariadb.com/docs/release-notes/community-server/10.11/what-is-mariadb-1011" target="_blank" rel="noopener">MariaDB 10.11 Changes &amp; Improvements</a><br>
<em>MariaDB 10.11 is a long-term maintenance release series, maintained until February 2028.</em></li>
<li><a href="https://mariadb.com/docs/release-notes/community-server/11.4/what-is-mariadb-114" target="_blank" rel="noopener">MariaDB 11.4 Changes &amp; Improvements</a><br>
<em>MariaDB 11.4 is a current long-term series, maintained until May 2029.</em></li>
<li><a href="https://mariadb.com/docs/release-notes/community-server/11.8/what-is-mariadb-118" target="_blank" rel="noopener">MariaDB 11.8 Changes &amp; Improvements</a><br>
<em>MariaDB 11.8 is a long-term release, maintained until June 2028.</em></li>
<li><a href="https://mariadb.com/docs/release-notes/community-server/12.3/mariadb-12.3-changes-and-improvements" target="_blank" rel="noopener">MariaDB 12.3 Changes &amp; Improvements</a><br>
<em>MariaDB 12.3 is a long term release, maintained until June 2029.</em></li>
</ul>

<p>The post <a rel="nofollow" href="https://www.fromdual.com/blog/mariadb-shortens-maintenance-period-from-5-to-3-years/">MariaDB shortens maintenance period from 5 to 3 years</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Connector/C 3.4.9, and 3.3.19 now available</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/mariadb-connector-c-3-4-9-and-3-3-19-now-available/" />
      <id>https://mariadb.com/resources/blog/mariadb-connector-c-3-4-9-and-3-3-19-now-available/</id>
      <updated>2026-06-10T17:42:44+00:00</updated>
      <author><name>Daniel Bartholomew</name></author>
      <summary type="html"><![CDATA[<p>MariaDB is pleased to announce the immediate availability of MariaDB Connector/C 3.4.9, and 3.3.19. Download Now Release Notes and Changelogs […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-connector-c-3-4-9-and-3-3-19-now-available/">MariaDB Connector/C 3.4.9, and 3.3.19 now available</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB is pleased to announce the immediate availability of MariaDB Connector/C 3.4.9, and 3.3.19. Download Now Notable items: Notable items: See the release notes and changelogs for more details and visit mariadb.com/downloads/connectors to download.</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-connector-c-3-4-9-and-3-3-19-now-available/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-connector-c-3-4-9-and-3-3-19-now-available/">MariaDB Connector/C 3.4.9, and 3.3.19 now available</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Vector Support Upstreamed to Open-WebUI: Single-Database RAG Just Got Faster and Simpler</title>
      <link rel="alternate" type="text/html" href="https://shatteredsilicon.net/mariadb-vector-open-webui/" />
      <id>https://shatteredsilicon.net/mariadb-vector-open-webui/</id>
      <updated>2026-06-10T12:21:46+00:00</updated>
      <author><name>Gordan Bobic</name></author>
      <summary type="html"><![CDATA[<p>At Shattered Silicon, we live at the intersection of high-performance databases and production-grade AI. As an open-source contributor in the MariaDB ecosystem and a serious player bridging relational databases with modern AI workloads, we are excited to share our upstream contribution to one of the most popular self-hosted AI platforms: Open-WebUI. Why MariaDB Vector Changes […]<br />
The post MariaDB Vector Support Upstreamed to Open-WebUI: Single-Database RAG Just Got Faster and Simpler appeared first on Shattered Silicon.</p>
<p>The post <a rel="nofollow" href="https://shatteredsilicon.net/mariadb-vector-open-webui/">MariaDB Vector Support Upstreamed to Open-WebUI: Single-Database RAG Just Got Faster and Simpler</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>At Shattered Silicon, we live at the intersection of high-performance databases and production-grade AI. As an open-source contributor in the MariaDB ecosystem and a serious player bridging relational databases with modern AI workloads, we are excited to share our upstream contribution to one of the most popular self-hosted AI platforms: Open-WebUI. Why MariaDB Vector Changes [&hellip;]</p>
<p>The post <a rel="nofollow" href="https://shatteredsilicon.net/mariadb-vector-open-webui/">MariaDB Vector Support Upstreamed to Open-WebUI: Single-Database RAG Just Got Faster and Simpler</a> appeared first on <a rel="nofollow" href="https://shatteredsilicon.net">Shattered Silicon</a>.</p>

<p>The post <a rel="nofollow" href="https://shatteredsilicon.net/mariadb-vector-open-webui/">MariaDB Vector Support Upstreamed to Open-WebUI: Single-Database RAG Just Got Faster and Simpler</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Server 12.3, 11.8, 11.4, 10.11, 10.6 – May 2026’s releases: thank you for your contributions</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-server-12-3-11-8-11-4-10-11-10-6-may-2026s-releases-thank-you-for-your-contributions/" />
      <id>https://mariadb.org/mariadb-server-12-3-11-8-11-4-10-11-10-6-may-2026s-releases-thank-you-for-your-contributions/</id>
      <updated>2026-06-10T11:38:37+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>On May… we have released an update of our 5 current LTS releases:<br />
These new releases contain a large amount of external contributions. The number of contributors is constantly growing, which is great! …<br />
Continue reading \"MariaDB Server 12.3, 11.8, 11.4, 10.11, 10.6 – May 2026’s releases: thank you for your contributions\"<br />
The post MariaDB Server 12.3, 11.8, 11.4, 10.11, 10.6 – May 2026’s releases: thank you for your contributions appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-server-12-3-11-8-11-4-10-11-10-6-may-2026s-releases-thank-you-for-your-contributions/">MariaDB Server 12.3, 11.8, 11.4, 10.11, 10.6 – May 2026’s releases: thank you for your contributions</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>On May&hellip; we have released an update of our 5 current LTS releases:<br>
These new releases contain a large amount of external contributions. The number of contributors is constantly growing, which is great! &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-server-12-3-11-8-11-4-10-11-10-6-may-2026s-releases-thank-you-for-your-contributions/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Server 12.3, 11.8, 11.4, 10.11, 10.6 &ndash; May 2026&rsquo;s releases: thank you for your contributions&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-server-12-3-11-8-11-4-10-11-10-6-may-2026s-releases-thank-you-for-your-contributions/">MariaDB Server 12.3, 11.8, 11.4, 10.11, 10.6 &ndash; May 2026&rsquo;s releases: thank you for your contributions</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-server-12-3-11-8-11-4-10-11-10-6-may-2026s-releases-thank-you-for-your-contributions/">MariaDB Server 12.3, 11.8, 11.4, 10.11, 10.6 – May 2026’s releases: thank you for your contributions</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Node.js Connector 3.5.3 and 3.4.6 now available</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/mariadb-node-js-connector-3-5-3-and-3-4-6-now-available/" />
      <id>https://mariadb.com/resources/blog/mariadb-node-js-connector-3-5-3-and-3-4-6-now-available/</id>
      <updated>2026-06-09T19:46:19+00:00</updated>
      <author><name>Daniel Bartholomew</name></author>
      <summary type="html"><![CDATA[<p>MariaDB is pleased to announce the immediate availability of the MariaDB Connector/Node.js 3.5.3 and 3.4.6 GA releases. Download Now Release […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-node-js-connector-3-5-3-and-3-4-6-now-available/">MariaDB Node.js Connector 3.5.3 and 3.4.6 now available</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB is pleased to announce the immediate availability of the MariaDB Connector/Node.js 3.5.3 and 3.4.6 GA releases. Download Now MariaDB Connector/Node.js 3.5.3 is a Stable (GA) release. Notable changes in this release include: MariaDB Connector/Node.js 3.4.6 is a Stable (GA) release. Notable changes in this release include: See&hellip;</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-node-js-connector-3-5-3-and-3-4-6-now-available/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-node-js-connector-3-5-3-and-3-4-6-now-available/">MariaDB Node.js Connector 3.5.3 and 3.4.6 now available</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Comprehensive Self-Service Backups for Continuous Data Protection in MariaDB Cloud</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/comprehensive-self-service-backups-for-continuous-data-protection-in-mariadb-cloud/" />
      <id>https://mariadb.com/resources/blog/comprehensive-self-service-backups-for-continuous-data-protection-in-mariadb-cloud/</id>
      <updated>2026-06-09T18:57:49+00:00</updated>
      <author><name>Naman Shah</name></author>
      <summary type="html"><![CDATA[<p>The MariaDB Cloud Backup Service provides organizations with a fully managed service for continuous data protection, mitigating risks from hardware […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/comprehensive-self-service-backups-for-continuous-data-protection-in-mariadb-cloud/">Comprehensive Self-Service Backups for Continuous Data Protection in MariaDB Cloud</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>The MariaDB Cloud Backup Service provides organizations with a fully managed service for continuous data protection, mitigating risks from hardware failure, zonal disruptions, data corruption, and cyberattacks. By offering a comprehensive API and intuitive interface, this service allows companies to automate recovery strategies tailored to specific compliance and business continuity requirements.</p>
<p><a href="https://mariadb.com/resources/blog/comprehensive-self-service-backups-for-continuous-data-protection-in-mariadb-cloud/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/comprehensive-self-service-backups-for-continuous-data-protection-in-mariadb-cloud/">Comprehensive Self-Service Backups for Continuous Data Protection in MariaDB Cloud</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>DuckDB Storage Engine for MariaDB. When the Sea Lion Learns to Quack.</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/duckdb-storage-engine-for-mariadb-when-the-sea-lion-learns-to-quack/" />
      <id>https://mariadb.org/duckdb-storage-engine-for-mariadb-when-the-sea-lion-learns-to-quack/</id>
      <updated>2026-06-09T16:30:22+00:00</updated>
      <author><name>Roman Nozdrin</name></author>
      <summary type="html"><![CDATA[<p>An early look at the DuckDB storage engine for MariaDB — columnar, vectorized analytics that live right next to your transactional tables.<br />
The problem<br />
MariaDB’s InnoDB is excellent at what it was built for: transactions. …<br />
Continue reading \"DuckDB Storage Engine for MariaDB. When the Sea Lion Learns to Quack.\"<br />
The post DuckDB Storage Engine for MariaDB. When the Sea Lion Learns to Quack. appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/duckdb-storage-engine-for-mariadb-when-the-sea-lion-learns-to-quack/">DuckDB Storage Engine for MariaDB. When the Sea Lion Learns to Quack.</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>An early look at the DuckDB storage engine for MariaDB &mdash; columnar, vectorized analytics that live right next to your transactional tables.<br>
The problem<a id="the-problem"></a><br>
MariaDB&rsquo;s InnoDB is excellent at what it was built for: transactions. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/duckdb-storage-engine-for-mariadb-when-the-sea-lion-learns-to-quack/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;DuckDB Storage Engine for MariaDB. When the Sea Lion Learns to Quack.&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/duckdb-storage-engine-for-mariadb-when-the-sea-lion-learns-to-quack/">DuckDB Storage Engine for MariaDB. When the Sea Lion Learns to Quack.</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/duckdb-storage-engine-for-mariadb-when-the-sea-lion-learns-to-quack/">DuckDB Storage Engine for MariaDB. When the Sea Lion Learns to Quack.</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Managing ClickHouse Resources in Multi-Tenant  Environments</title>
      <link rel="alternate" type="text/html" href="https://severalnines.com/blog/managing-clickhouse-resources-in-multi-tenant-environments/" />
      <id>https://severalnines.com/blog/managing-clickhouse-resources-in-multi-tenant-environments/</id>
      <updated>2026-06-09T11:20:22+00:00</updated>
      <author><name>Sucahyo Ardy Prasetiyo</name></author>
      <summary type="html"><![CDATA[<p>When people first deploy ClickHouse, their initial reaction is often surprise. Queries that used to take minutes now finish in seconds. Dashboards feel instant even when reading billions of rows. To see this in action, here is a simple aggregation query running against a 200 million row events table: ClickHouse delivers exceptional speed, scanning 200 […]<br />
The post Managing ClickHouse Resources in Multi-Tenant Environments appeared first on Severalnines.</p>
<p>The post <a rel="nofollow" href="https://severalnines.com/blog/managing-clickhouse-resources-in-multi-tenant-environments/">Managing ClickHouse Resources in Multi-Tenant  Environments</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>When people first deploy ClickHouse, their initial reaction is often surprise. Queries that used to take minutes now finish in seconds. Dashboards feel instant even when reading billions of rows. </p>
<p>To see this in action, here is a simple aggregation query running against a 200 million row events table:</p>
<pre class="wp-block-code"><code>SELECT customer_id, count()
FROM my_db.events GROUP BY customer_id ORDER BY count() DESC;</code></pre>
<p>ClickHouse delivers exceptional speed, scanning 200 million rows in under a second on a 3-node cluster. This efficiency powers real-time analytics and observability platforms.</p>
<p>However, production environments are often multi-tenant, where dashboards, ETL pipelines, and background processes share CPU, memory, and disk resources. Without proper resource management, greedy workloads can saturate the system, causing performance degradation across all tasks.</p>
<p>This post explores operational strategies for managing ClickHouse in shared environments. Using a live 3-node replicated cluster with 200 million rows, we demonstrate how to identify contention, implement workload scheduling, and validate system stability.</p>
<h2 class="wp-block-heading" id="h-understanding-resource-contention-in-clickhouse">Understanding Resource Contention in ClickHouse<a class="anchor-link" id="understanding-resource-contention-in-clickhouse"></a></h2>
<p>ClickHouse is built for analytical processing. It scans large datasets fast by spreading work across many CPU threads at the same time. That design is what makes it so quick. But it also means that when multiple workloads run together, they start competing for the same resources at the same time.</p>
<p>This is different from databases like MySQL or PostgreSQL. In those systems, contention usually shows up as lock waits or transaction conflicts. In ClickHouse, the problem is almost always infrastructure saturation.</p>
<p>Take this query running against our 200 million row events table:</p>
<pre class="wp-block-code"><code>SELECT customer_id, count()
FROM my_db.events GROUP BY customer_id ORDER BY count() DESC;</code></pre>
<p>This query looks simple but it scans all 200 million rows, builds aggregation buffers in memory, uses multiple CPU threads in parallel, and reads a significant amount of data from disk. Run one and the cluster handles it fine. Run several at the same time and things start to break down. You can verify this directly by checking the query log:</p>
<pre class="wp-block-code"><code>SELECT query_duration_ms, read_rows, read_bytes, memory_usage
FROM system.query_log
WHERE type = 'QueryFinish' AND query LIKE '%customer_id%'
ORDER BY event_time DESC LIMIT 5;</code></pre>
<p>This gets even more complicated in multi-tenant environments. A tenant can be a different team, a different application, or a different customer all sharing the same cluster at the same time. The challenges are real. One heavy query slows down everyone else. </p>
<p>Without proper row policies data can leak between tenants. Without resource controls one tenant can consume everything and leave nothing for others. And without careful schema design, performance problems become very hard to fix later. These are not just performance problems. In multi-tenant environments they become operational risks.</p>
<p>When contention builds up, operators start noticing these symptoms: CPU stays near 100% even between queries, dashboard responses get slower, replication starts falling behind, merge queues keep growing, insert throughput drops, and network pressure is also real. In our 3 node setup every insert gets replicated to two other nodes at the same time. </p>
<p>During heavy inserts, replication traffic and query traffic compete for the same network interface and replication lag starts climbing:</p>
<pre class="wp-block-code"><code>SELECT replica_name, absolute_delay, queue_size, inserts_in_queue
FROM system.replicas
ORDER BY absolute_delay DESC;</code></pre>
<p>ClickHouse is not broken when this happens. It is doing exactly what it was designed to do, which is use every available resource to finish analytical work as fast as possible. The job of the operator is to make sure no single workload takes more than its fair share.</p>
<p><strong>That is what the rest of this article is about.</strong></p>
<h3 class="wp-block-heading" id="h-cpu-contention-and-thread-management">CPU Contention and Thread Management<a class="anchor-link" id="cpu-contention-and-thread-management"></a></h3>
<p>In shared ClickHouse environments, CPU contention is frequent because the system defaults to using maximum threads for speed. While effective for single queries, concurrent workloads compete for threads, overwhelming the CPU.</p>
<p>A common way to control this is with the <code>max_threads</code> setting: <code>SET max_threads = 4;</code></p>
<p>The first reaction most people have is, &ldquo;Why would I want to make my queries slower?&rdquo; The honest answer is that fewer threads does not always mean slower. Sometimes it means faster.</p>
<p>We tested this directly on our 3 node cluster with 200 million rows. We ran the same query under different conditions and checked the query log:</p>
<pre class="wp-block-code"><code>SELECT query_duration_ms, read_rows, Settings['max_threads'] AS max_threads 
FROM system.query_log
WHERE type = 'QueryFinish' AND query LIKE '%customer_id%' ORDER BY event_time DESC LIMIT 4;</code></pre>
<p>In a shared cluster the benefit becomes even more obvious. When 10 analysts run queries at the same time on a 32 core server and each query tries to use 16 threads, that is 160 threads competing for 32 cores. The CPU scheduler gets overwhelmed and everything slows down together. By giving each query fewer threads the cluster stays stable and responsive for everyone.</p>
<p>Think of it this way. A single lane highway moves fast until everyone tries to use it at once. Splitting into more lanes and slowing everyone down slightly keeps traffic moving for all users.</p>
<p><strong>When lowering <code>max_threads</code> makes sense:</strong></p>
<ul class="wp-block-list">
<li>A shared cluster where many users run queries at the same time</li>
<li>Dashboard workloads that need consistent low latency</li>
<li>Environments where insert pipelines and merges need to keep running alongside analytical queries</li>
</ul>
<p><strong>When raising <code>max_threads</code> makes sense:</strong></p>
<ul class="wp-block-list">
<li>A dedicated batch environment running a small number of heavy jobs</li>
<li>Overnight ETL workloads where the cluster is mostly idle</li>
<li>Single user environments where there is no competition for resources</li>
</ul>
<p>The most important thing to understand is that more threads is not always better. The right value always depends on your hardware, your data, and how many workloads are sharing the cluster at the same time.</p>
<h3 class="wp-block-heading" id="h-memory-management-and-query-stability">Memory Management and Query Stability<a class="anchor-link" id="memory-management-and-query-stability"></a></h3>
<p>Memory is the next resource that gets squeezed in a shared ClickHouse environment. Analytical queries are hungry for memory. Operations like GROUP BY, JOIN, sorting, DISTINCT, and distributed aggregations all need to build large temporary buffers while they run.</p>
<p>The setting that controls this is <code>SET max_memory_usage = '1G';</code></p>
<p>This limits how much memory a single query can use. Most people assume that giving queries more memory is always better because they finish faster. In practice that thinking is one of the fastest ways to destabilize a shared cluster.</p>
<p>Our 3 node cluster is a good real world example of this. Each node has 4GB of total RAM with no swap configured. Here is the actual memory picture on each node:</p>
<pre class="wp-block-code"><code>               total        used        free      
available
Mem:           4.0Gi       1.7Gi       2.1Gi       
2.3Gi
Swap:             0B          0B          0B</code></pre>
<p>ClickHouse is already consuming around 415MB just to keep the server running. That leaves roughly 2.3GB actually available for queries, merges, replication, and the operating system to share.</p>
<p>The default <code>max_memory_usage</code> is set to 0 which means unlimited. On a node with no swap that is dangerous. If a query tries to allocate more memory than the node has available, the operating system will immediately kill the ClickHouse process. There is no swap to fall back on. The process just dies. You can verify your current memory usage and limit with these queries:</p>
<pre class="wp-block-code"><code>SELECT metric, value FROM system.metrics WHERE metric LIKE '%Memory%';
SELECT name, value FROM system.settings WHERE name = 'max_memory_usage';</code></pre>
<p>On our cluster the result looks like this:</p>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="694" height="120" src="https://severalnines.com/wp-content/uploads/2026/05/clickhouse-system-metrics-memory-tracking.png" alt="ClickHouse system metrics output (likely from system.metrics) detailing active memory tracking counters, including total MemoryTracking at ~389.14 million bytes and serialization cache sizes." class="wp-image-43522"></figure>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="557" height="57" src="https://severalnines.com/wp-content/uploads/2026/05/clickhouse-settings-max-memory-usage.png" alt="Query output showing the max_memory_usage setting or profile parameter value, currently set to 0 (typically indicating unlimited or unrestricted memory for the context)." class="wp-image-43523"></figure>
<p>The fix is a per query limit in the user config and a server wide cap in <code>/etc/clickhouse-server/config.d/memory.xml</code>. For our environment we set <code>max_memory_usage</code> to 1GB per query and <code>max_server_memory_usage</code> to 3GB total. This leaves 1GB free for the OS, Keeper, and background processes.</p>
<p>When the limit is hit users will see <code>MEMORY_LIMIT_EXCEEDED</code>. That error is actually a good sign. It means the limit is working and protecting the node from going down entirely.</p>
<p>But setting limits too low creates the opposite problem. Some workloads genuinely need large buffers. If limits are too tight legitimate queries start failing.</p>
<p><strong>When lowering <code>max_memory_usage</code> makes sense:</strong></p>
<ul class="wp-block-list">
<li>A shared cluster with many concurrent users</li>
<li>Nodes with limited RAM and no swap like our environment</li>
<li>Environments prone to sudden traffic spikes</li>
</ul>
<p><strong>When raising <code>max_memory_usage</code> makes sense:</strong></p>
<ul class="wp-block-list">
<li>Isolated reporting workloads running on a schedule</li>
<li>Heavy ETL jobs running during off peak hours</li>
<li>Dedicated nodes with higher memory capacity</li>
</ul>
<p>On our 4GB nodes with no swap, keeping memory limits tight is not optional; it is what keeps the cluster alive.</p>
<h3 class="wp-block-heading" id="h-disk-i-o-and-merge-pressure">Disk I/O and Merge Pressure<a class="anchor-link" id="disk-i-o-and-merge-pressure"></a></h3>
<p>Disk behavior in ClickHouse is very different from most traditional databases because of how the MergeTree engine works. Every insert gets written as a new immutable part on disk. A background process continuously merges these small parts into larger ones to keep storage efficient and queries fast. Without merges, parts accumulate, queries slow down, and storage becomes fragmented.</p>
<p>The most common way operators create merge problems without realizing it is by inserting data in very small batches. We simulated this on our cluster by running 1000 single row inserts in a loop. The parts count jumped significantly with each insert. You can see this directly by checking parts before and after:</p>
<pre class="wp-block-code"><code>SELECT database, table, count() AS parts_count, sum(rows) AS total_rows
FROM system.parts
WHERE active = 1 AND database = 'my_db'
GROUP BY database, table;</code></pre>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="645" height="90" src="https://severalnines.com/wp-content/uploads/2026/05/clickhouse-table-parts-count-total-rows.png" alt="ClickHouse query result tracking table health metrics for my_db.events, indicating a parts_count of 71 across a massive dataset of 200 million total rows." class="wp-image-43524"></figure>
<p>Each tiny insert creates a new part on disk. This is what people call a merge explosion. The merge queue builds up faster than ClickHouse can clear it, disk I/O gets saturated from background merges competing with foreground queries, replication falls behind, and query performance drops because ClickHouse has to scan many more physical files.</p>
<p>The fix is simple. Insert data in large batches instead of small ones. Instead of 1 row at a time, insert at least 10,000 rows per batch. When we loaded 200 million rows in large batches the part count stayed manageable throughout.</p>
<p>You can monitor merge activity at any time with:</p>
<pre class="wp-block-code"><code>SELECT database, table, elapsed, progress, num_parts
FROM system.merges
ORDER BY elapsed DESC;</code></pre>
<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="678" height="59" src="https://severalnines.com/wp-content/uploads/2026/05/clickhouse-active-merges-in-progress.png" alt="Monitoring output from system.merges showing an active background data part merge operation (merges_in_progress: 1) currently running on the my_db.events MergeTree table." class="wp-image-43525"></figure>
<p>Operators can also tune background merge concurrency with <code>background_pool_size</code>. Higher values help clear backlogs faster but on our 4GB nodes with no swap, more merge threads means more memory and disk I/O competing with foreground queries at the same time.</p>
<p><strong>When increasing <code>background_pool_size</code> makes sense:</strong></p>
<ul class="wp-block-list">
<li>Merge queues are growing consistently and not clearing</li>
<li>Disks have spare I/O capacity</li>
<li>Nodes have enough RAM to handle additional merge threads</li>
</ul>
<p><strong>When keeping <code>background_pool_size</code> lower makes sense:</strong></p>
<ul class="wp-block-list">
<li>Disks are already saturated</li>
<li>Nodes have limited RAM like our 4GB environment</li>
<li>Query latency is more important than insert throughput</li>
</ul>
<p>Higher values do not automatically mean better performance. On constrained hardware like ours, keeping merge concurrency modest is what keeps queries responsive while background work continues steadily.</p>
<h2 class="wp-block-heading" id="h-workload-scheduling-and-prioritization">Workload Scheduling and Prioritization<a class="anchor-link" id="workload-scheduling-and-prioritization"></a></h2>
<p>Even with thread and memory limits in place, a shared cluster still struggles when different workload types compete for the same resources at the same time. Dashboard queries need millisecond responses. ETL jobs can take minutes. Without scheduling, both are treated equally and dashboards suffer. ClickHouse solves this with a workload scheduling system that controls how disk IO, CPU threads, and query slots are shared between workloads.</p>
<pre class="wp-block-code"><code>root
&#9500;&#9472;&#9472; realtime
&#9474;   &#9500;&#9472;&#9472; dashboards
&#9474;   &#9492;&#9472;&#9472; api_queries
&#9500;&#9472;&#9472; batch
&#9474;   &#9500;&#9472;&#9472; etl
&#9474;   &#9492;&#9472;&#9472; exports
&#9492;&#9472;&#9472; background
   &#9500;&#9472;&#9472; merges
   &#9492;&#9472;&#9472; replication</code></pre>
<h3 class="wp-block-heading" id="h-scheduling-hierarchy-and-resource-definitions">Scheduling Hierarchy and Resource Definitions<a class="anchor-link" id="scheduling-hierarchy-and-resource-definitions"></a></h3>
<p>The foundation of workload scheduling in ClickHouse is the concept of a resource. A resource represents a shared physical asset that multiple workloads compete for. ClickHouse supports three types: disk IO, CPU threads, and query slots.</p>
<p>Start by defining what resources exist on your cluster:</p>
<pre class="wp-block-code"><code>CREATE RESOURCE disk_read (READ ANY DISK);
CREATE RESOURCE disk_write (WRITE ANY DISK);
CREATE RESOURCE cpu (MASTER THREAD, WORKER THREAD);
CREATE RESOURCE query (QUERY);</code></pre>
<p>The READ and WRITE disk definitions are important. They let you control read and write IO separately. In a shared cluster, dashboard read traffic and insert write traffic compete for the same disk bandwidth. Separating them gives you independent control over each.</p>
<p>Once resources are defined, build a workload hierarchy on top of them. The root workload sits at the top and distributes resources down to everything below it:</p>
<pre class="wp-block-code"><code>CREATE WORKLOAD root
SETTINGS
    max_concurrent_threads = 50,
    max_concurrent_queries = 50,
    max_queries_per_second = 20;

CREATE WORKLOAD realtime IN root SETTINGS priority = 1;
CREATE WORKLOAD batch IN root SETTINGS priority = 10;
CREATE WORKLOAD background IN root SETTINGS priority = 100;</code></pre>
<p>You can also apply bandwidth limits per resource directly on a workload. This caps read bandwidth at 100 MB/s and write bandwidth at 50 MB/s:</p>
<pre class="wp-block-code"><code>CREATE WORKLOAD all IN root
SETTINGS
    max_bytes_per_second = 104857600 FOR disk_read,
    max_bytes_per_second = 52428800 FOR disk_write;</code></pre>
<p>The root workload manages resource distribution across the hierarchy. High-priority &ldquo;realtime&rdquo; traffic like dashboards requires fast, consistent responses. The &ldquo;batch&rdquo; branch handles latency-tolerant tasks such as ETL pipelines, while &ldquo;background&rdquo; operations like replication run steadily without impacting foreground performance.</p>
<p>You can verify which workloads exist on your cluster:</p>
<pre class="wp-block-code"><code>SELECT * FROM system.workloads;
SELECT * FROM system.resources;</code></pre>
<p>In ClickHouse lower priority numbers mean higher priority. Realtime gets served first, then batch, then background. Assign users to workloads by creating dedicated users:</p>
<pre class="wp-block-code"><code>CREATE USER dashboard_user IDENTIFIED BY 'dashboard123'
SETTINGS workload = 'realtime';

CREATE USER analyst IDENTIFIED BY 'analyst123'
SETTINGS workload = 'batch';</code></pre>
<p>You can also assign workloads through the user config file for existing users. Add the workload setting to <code>/etc/clickhouse-server/users.d/default-password.xml</code></p>
<p><strong>N.B. A common mistake is giving all workloads equal priority.</strong> When a heavy batch job and a lightweight dashboard query compete equally, the batch job almost always wins because it consumes more resources per query. Proper prioritization flips this; the batch job still runs, it just waits its turn when realtime traffic needs resources first.</p>
<h3 class="wp-block-heading" id="h-memory-overcommit-and-query-queueing">Memory Overcommit and Query Queueing<a class="anchor-link" id="memory-overcommit-and-query-queueing"></a></h3>
<p>Memory overcommit controls what happens when total memory demand from all running queries exceeds what is physically available. On our 4GB nodes with no swap this is critical. Without overcommit controls, if multiple queries simultaneously try to allocate more memory than is available the OS kills the ClickHouse process immediately.</p>
<p>ClickHouse handles this by waiting briefly for other queries to release memory before terminating the most overcommitted query first. This is much safer than having no limit at all:</p>
<pre class="wp-block-code"><code>SET max_memory_usage = 1073741824;
SET memory_usage_overcommit_max_wait_microseconds = 5000000;</code></pre>
<p>Instead of the entire node going down, only the most memory hungry query gets cancelled. Everything else keeps running. On our 4GB nodes this is the difference between a graceful query failure and a full cluster crash.</p>
<p>Query queueing handles overload at the concurrency level. When more queries arrive than the cluster can handle they queue up instead of all running at once. You can set this at the server level <code>in /etc/clickhouse-server/config.d/cluster.xml</code>. Or via workload scheduling:</p>
<pre class="wp-block-code"><code>CREATE OR REPLACE WORKLOAD root SETTINGS
    max_concurrent_threads = 50,
    max_concurrent_queries = 50,
    max_queries_per_second = 20;</code></pre>
<p>On our 4GB nodes, 50 concurrent queries is a safe ceiling. New queries that arrive when the limit is hit wait for a slot instead of crashing the node. Monitor active and queued queries at any time:</p>
<pre class="wp-block-code"><code>SELECT query, elapsed, memory_usage, read_rows
FROM system.processes
ORDER BY elapsed DESC;</code></pre>
<p>When lowering <code>max_concurrent_queries</code> makes sense:</p>
<ul class="wp-block-list">
<li>Nodes with limited RAM like our 4GB environment</li>
<li>Clusters with no swap configured</li>
<li>Environments where query stability matters more than raw throughput</li>
</ul>
<p>When raising <code>max_concurrent_queries</code> makes sense:</p>
<ul class="wp-block-list">
<li>Nodes with large amounts of RAM and fast disks</li>
<li>Clusters serving many lightweight queries simultaneously</li>
<li>Environments where queries are short and memory usage per query is low</li>
</ul>
<p>A cluster managing a queue is more often more stable than one where unlimited queries run simultaneously. On constrained hardware, queueing is not a limitation but what keeps the cluster alive under pressure. When workload scheduling makes the most difference:</p>
<ul class="wp-block-list">
<li>Customer facing dashboards sharing a cluster with internal ETL jobs</li>
<li>Clusters serving multiple teams with different SLA requirements</li>
<li>Environments where insert pipelines and analytical queries run simultaneously</li>
</ul>
<p><strong>N.B. The goal is not to make batch jobs slow.</strong> The goal is to make sure realtime workloads stay fast even when the cluster is under pressure.</p>
<h2 class="wp-block-heading" id="h-isolation-strategies-in-multi-tenant-environments">Isolation Strategies in Multi-Tenant Environments<a class="anchor-link" id="isolation-strategies-in-multi-tenant-environments"></a></h2>
<p>Everything we have covered so far assumes different workloads share the same cluster. That works well up to a point; but, some organizations eventually reach a scale where sharing creates too much risk. One bad query from one tenant can still affect everyone else no matter how carefully the limits are tuned &mdash; this is the noisy neighbor problem. The solution depends on how much isolation you actually need. There are three main ways organizations handle this depending on their scale and operational maturity.</p>
<h3 class="wp-block-heading" id="h-approach-1-shared-cluster-with-schema-level-isolation">Approach 1: Shared Cluster with Schema Level Isolation<a class="anchor-link" id="approach-1-shared-cluster-with-schema-level-isolation"></a></h3>
<p>This is the most common starting point. All tenants share the same cluster and the same table. Isolation is handled through schema design and row policies.</p>
<p>The most important thing to get right is the schema. Including <code>tenant_id</code> in the sorting key makes a significant difference:</p>
<pre class="wp-block-code"><code>CREATE TABLE my_db.events ON CLUSTER my_cluster
(
    tenant_id       UInt32,
    event_date      Date,
    event_id        UInt64,
    customer_id     UInt32,
    event_type      LowCardinality(String),
    event_timestamp DateTime,
    metadata        String
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
PARTITION BY toYYYYMM(event_date)
ORDER BY (tenant_id, event_date, customer_id, event_id);</code></pre>
<p>With <code>tenant_id</code> first in the sort key, ClickHouse physically stores each tenant&rsquo;s data together on disk. A query filtering by <code>tenant_id</code> only reads that tenant&rsquo;s data and skips everything else.</p>
<p>For dashboard workloads, pre-aggregate data per tenant into materialized views instead of letting tenants query the raw table directly:</p>
<pre class="wp-block-code"><code>CREATE MATERIALIZED VIEW my_db.events_tenant1_mv
ENGINE = SummingMergeTree()
ORDER BY (event_date, customer_id)
AS SELECT
    event_date,
    customer_id,
    count() AS event_count
FROM my_db.events
WHERE tenant_id = 1
GROUP BY event_date, customer_id;</code></pre>
<p>This keeps tenant queries physically separated and pre-computed so one tenant&rsquo;s heavy scan cannot slow down another&rsquo;s dashboard.</p>
<p>Then enforce data isolation with restrictive row policies:</p>
<pre class="wp-block-code"><code>-- Grant access
GRANT SELECT ON my_db.events TO tenant1_user;
GRANT SELECT ON my_db.events TO tenant2_user;

-- Create restrictive row policies
CREATE ROW POLICY tenant1_policy ON my_db.events
AS RESTRICTIVE
FOR SELECT USING tenant_id = 1
TO tenant1_user;

CREATE ROW POLICY tenant2_policy ON my_db.events
AS RESTRICTIVE
FOR SELECT USING tenant_id = 2
TO tenant2_user;

-- Verify policies
SELECT short_name, select_filter, is_restrictive, apply_to_list
FROM system.row_policies
WHERE table = 'events';</code></pre>
<p>On our cluster with 200 million rows distributed across 5 tenants, each tenant user can only see their own 40 million rows and gets zero results when querying other tenant data.</p>
<h3 class="wp-block-heading" id="h-approach-2-database-level-isolation">Approach 2: Database Level Isolation<a class="anchor-link" id="approach-2-database-level-isolation"></a></h3>
<p>A step up from row policies. Each tenant gets their own database but shares the same cluster infrastructure:</p>
<pre class="wp-block-code"><code>CREATE DATABASE tenant1_db ON CLUSTER my_cluster;
CREATE DATABASE tenant2_db ON CLUSTER my_cluster;
CREATE TABLE tenant1_db.events ON CLUSTER my_cluster
(
    event_date      Date,
    event_id        UInt64,
    customer_id     UInt32,
    event_type      LowCardinality(String),
    event_timestamp DateTime,
    metadata        String
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/tenant1/events', '{replica}')
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, customer_id, event_id);</code></pre>
<p>This gives cleaner separation and makes per tenant storage, backups, and access controls easier to manage. The tradeoff is more tables to maintain as tenant count grows.</p>
<h3 class="wp-block-heading" id="h-approach-3-cluster-level-isolation">Approach 3: Cluster Level Isolation<a class="anchor-link" id="approach-3-cluster-level-isolation"></a></h3>
<p>The strongest form of isolation. Different workload types get entirely separate clusters:</p>
<figure class="wp-block-table">
<table class="has-fixed-layout">
<tbody>
<tr>
<td>Ingestion cluster</td>
<td>handles high throughput data loading</td>
</tr>
<tr>
<td>Dashboard cluster</td>
<td>optimized for low latency concurrent reads</td>
</tr>
<tr>
<td>ETL cluster</td>
<td>reserved for heavy transformation jobs</td>
</tr>
</tbody>
</table>
</figure>
<p>This eliminates the noisy neighbor problem completely. The tradeoff is higher infrastructure cost and more operational complexity.</p>
<h3 class="wp-block-heading" id="h-using-settings-to-prioritize-workloads">Using Settings to Prioritize Workloads<a class="anchor-link" id="using-settings-to-prioritize-workloads"></a></h3>
<p>Beyond isolation approach, individual query settings give operators per query control over resource consumption per tenant without changing global settings:</p>
<pre class="wp-block-code"><code>-- Heavy report query - limit resources
SELECT customer_id, count()
FROM my_db.events
WHERE tenant_id = 1
GROUP BY customer_id
SETTINGS max_threads = 4, max_memory_usage = 1073741824, workload = 'batch';

-- Dashboard query - allow more resources
SELECT count()
FROM my_db.events
WHERE tenant_id = 1
AND event_date = today()
SETTINGS max_threads = 8, workload = 'realtime';</code></pre>
<p>A heavy analytical report from one tenant can be throttled while their dashboard queries remain fast.</p>
<h3 class="wp-block-heading" id="h-choosing-the-right-approach">Choosing the Right Approach<a class="anchor-link" id="choosing-the-right-approach"></a></h3>
<figure class="wp-block-table">
<table class="has-fixed-layout">
<tbody>
<tr>
<td>Approach</td>
<td>Best for</td>
<td>Tradeoff</td>
</tr>
<tr>
<td>Shared cluster with row policies</td>
<td>Small tenant count, limited hardware</td>
<td>Noisy neighbor risk remains</td>
</tr>
<tr>
<td>Separate databases per tenant</td>
<td>Medium tenant count, cleaner isolation</td>
<td>More tables to manage</td>
</tr>
<tr>
<td>Dedicated clusters</td>
<td>Large scale, strict SLAs</td>
<td>Higher cost and complexity</td>
</tr>
</tbody>
</table>
</figure>
<p>Most organizations start with Approach 1, move to Approach 2 as tenant count grows, and only adopt Approach 3 when SLA requirements become strict enough to justify the cost. For our 3 node cluster with 4GB RAM per node, Approach 1 with row policies, materialized views, and workload assignment is the most practical starting point.</p>
<h2 class="wp-block-heading" id="h-operational-best-practices">Operational Best Practices<a class="anchor-link" id="operational-best-practices"></a></h2>
<p>Resource issues in ClickHouse rarely announce themselves immediately. A cluster can look perfectly healthy from the outside while merge queues, memory pressure, or replication lag quietly build up internally. By the time users start complaining the problem has usually been growing for a while. This is why operational visibility and proper configuration are just as important as the tuning settings we covered in earlier sections.</p>
<h3 class="wp-block-heading" id="h-setting-up-workload-classes-and-assigning-quotas">Setting Up Workload Classes and Assigning Quotas<a class="anchor-link" id="setting-up-workload-classes-and-assigning-quotas"></a></h3>
<pre class="wp-block-code"><code>CREATE WORKLOAD realtime IN root SETTINGS priority = 1;
CREATE WORKLOAD batch IN root SETTINGS priority = 10;
CREATE WORKLOAD background IN root SETTINGS priority = 100;</code></pre>
<p>Then assign users to workloads:</p>
<pre class="wp-block-code"><code>CREATE USER dashboard_user IDENTIFIED BY 'dashboard123'
SETTINGS workload = 'realtime';

CREATE USER analyst IDENTIFIED BY 'analyst123'
SETTINGS workload = 'batch';</code></pre>
<p>Quotas add a second layer of control on top of workload priority. Even if a user has high priority, quotas prevent them from consuming unlimited resources over time:</p>
<pre class="wp-block-code"><code>-- Tenant users: 1000 queries per hour, max 10 billion rows read
CREATE QUOTA tenant_quota
    FOR INTERVAL 1 HOUR
    MAX queries = 1000,
    MAX read_rows = 10000000000
    TO tenant1_user, tenant2_user;

-- Analysts: 100 queries per hour, max 5 billion rows read
CREATE QUOTA analyst_quota
    FOR INTERVAL 1 HOUR
    MAX queries = 100,
    MAX read_rows = 5000000000
    TO analyst;</code></pre>
<h2 class="wp-block-heading" id="h-monitoring-resource-usage">Monitoring Resource Usage<a class="anchor-link" id="monitoring-resource-usage"></a></h2>
<p><a href="https://severalnines.com/blog/clickhouse-monitoring-and-observability-decision-points/">Good monitoring practices</a> catch problems before they become visible to users. On our 3 node cluster with 200 million rows we focus on five key signals.</p>
<p><strong>Query latency</strong> is usually the first visible sign of contention:</p>
<pre class="wp-block-code"><code>SELECT query_duration_ms, read_rows, memory_usage, query
FROM system.query_log
WHERE type = 'QueryFinish'
AND event_time &gt;= now() - INTERVAL 10 MINUTE
ORDER BY query_duration_ms DESC
LIMIT 10;</code></pre>
<p><strong>Replication lag</strong> signals network pressure or overloaded replicas:</p>
<pre class="wp-block-code"><code>SELECT replica_name, absolute_delay, queue_size, inserts_in_queue
FROM system.replicas
ORDER BY absolute_delay DESC;</code></pre>
<p><strong>Parts growth</strong> indicates merge pressure from small inserts:</p>
<pre class="wp-block-code"><code>SELECT database, table, count() AS parts_count, sum(rows) AS total_rows
FROM system.parts
WHERE active = 1 AND database = 'my_db'
GROUP BY database, table
ORDER BY parts_count DESC;</code></pre>
<p>For a full cluster health snapshot combine all signals into one query:</p>
<pre class="wp-block-code"><code>SELECT
    (SELECT count() FROM system.processes) AS active_queries,
    (SELECT count() FROM system.merges) AS active_merges,
    (SELECT max(absolute_delay) FROM system.replicas) AS max_replication_delay,
    (SELECT max(queue_size) FROM system.replicas) AS max_replication_queue,
    (SELECT count() FROM system.parts WHERE active = 1 AND database = 'my_db') AS parts_count,
    (SELECT value FROM system.metrics WHERE metric = 'MemoryTracking' LIMIT 1) AS memory_used_bytes;</code></pre>
<p>Run this regularly and you will catch problems before they reach users.</p>
<p>One important thing to keep in mind is that tuning ClickHouse rarely eliminates a bottleneck completely. It usually just moves it somewhere else. Increasing <code>background_pool_size</code> may clear the merge queue faster but adds more disk I/O pressure. Lowering <code>max_memory_usage</code> may stabilize the cluster but some queries will start failing. The system tables covered in this section are your best tool for observing exactly what changed after each adjustment.</p>
<p>The best way to understand ClickHouse resource management is not to read about it but to test it directly on a real cluster with real data and watch what happens.</p>
<h2 class="wp-block-heading" id="h-integrating-with-ops-tooling">Integrating with Ops Tooling<a class="anchor-link" id="integrating-with-ops-tooling"></a></h2>
<p>Running ClickHouse in production is not just about tuning settings and writing good queries. At some point the cluster needs to integrate with the broader operational infrastructure that the rest of your organization already uses. Alerts need to fire before users notice problems. Capacity needs to grow before resource pressure becomes a crisis. And in organizations running multiple database technologies, policies need to be enforced consistently across all of them.</p>
<h3 class="wp-block-heading" id="h-alerts-for-resource-exhaustion">Alerts for Resource Exhaustion<a class="anchor-link" id="alerts-for-resource-exhaustion"></a></h3>
<p>ClickHouse exposes metrics via its HTTP interface that can be scraped by Prometheus or any compatible monitoring system: <code>curl http://server1:8123/metrics</code></p>
<figure class="wp-block-table">
<table class="has-fixed-layout">
<tbody>
<tr>
<td>Metric</td>
<td>Warning</td>
<td>Critical</td>
</tr>
<tr>
<td>Memory usage</td>
<td>&gt; 2.5GB</td>
<td>&gt; 3GB</td>
</tr>
<tr>
<td>Replication delay</td>
<td>&gt; 30s</td>
<td>&gt; 300s</td>
</tr>
<tr>
<td>Active merges</td>
<td>&gt; 10</td>
<td>&gt; 20</td>
</tr>
<tr>
<td>Concurrent queries</td>
<td>&gt; 40</td>
<td>&gt; 50</td>
</tr>
<tr>
<td>Parts count</td>
<td>&gt; 500</td>
<td>&gt; 1000</td>
</tr>
</tbody>
</table><figcaption class="wp-element-caption"><strong>Recommended alert thresholds for our 4GB nodes</strong></figcaption></figure>
<h2 class="wp-block-heading" id="h-scaling-out-nodes-and-shards-when-resource-pressure-builds">Scaling Out Nodes and Shards When Resource Pressure Builds<a class="anchor-link" id="scaling-out-nodes-and-shards-when-resource-pressure-builds"></a></h2>
<p>Our current setup is 1 shard with 3 replicas. When pressure builds consistently across all nodes it is a signal to grow. Scale up by adding more CPU or memory to existing nodes. Scale out by adding more shards to distribute data and query load across more hardware.</p>
<p>Signs it is time to scale out:</p>
<ul class="wp-block-list">
<li>CPU stays above 80% consistently,</li>
<li>memory errors appear regularly,</li>
<li>merge queues keep growing despite tuning,</li>
<li>and replication lag keeps climbing.</li>
</ul>
<h2 class="wp-block-heading" id="h-using-unified-management-to-enforce-policies-across-databases">Using Unified Management to Enforce Policies Across Databases<a class="anchor-link" id="using-unified-management-to-enforce-policies-across-databases"></a></h2>
<p>Organizations running ClickHouse alongside MySQL or PostgreSQL face the challenge of managing resource policies, backups, and monitoring separately for each technology. Purpose built database management tools provide a unified management layer across heterogeneous database environments. From a single interface operators can monitor ClickHouse alongside other databases, enforce consistent backup policies, manage user access across multiple clusters, and get unified alerting across all database technologies. The goal is not to replace ClickHouse native tooling. It is to reduce operational overhead when running ClickHouse as part of a larger database fleet.</p>
<h2 class="wp-block-heading" id="h-conclusion">Conclusion<a class="anchor-link" id="conclusion"></a></h2>
<p>ClickHouse is genuinely fast. But in shared environments, speed without governance becomes a liability. Throughout this article we ran real workloads against 200 million rows on constrained 4GB nodes to show exactly how contention happens and how to control it.<br>The key takeaways are simple. </p>
<p>Lower <code>max_threads</code> in shared clusters. Set <code>max_memory_usage</code> explicitly especially on nodes with no swap. Insert in large batches to avoid merge explosions. Assign workload classes so dashboards always get priority over batch jobs. Put your primary identifier column first in the sort key and enforce row policies per tenant.</p>
<p>Before going to production with a shared cluster run through this quick checklist:</p>
<p>Primary identifier column is first in the sort key<br>Row policies are set to <code>AS RESTRICTIVE</code><br><code>max_memory_usage</code> and <code>max_server_memory_usage</code> are explicitly set<br>Workload hierarchy is defined with realtime, batch, and background<br>Quotas are assigned per user type<br>Inserts are batched at minimum 10,000 rows<br>Health snapshot query is running regularly</p>
<p>Good ClickHouse operations are not about maximizing every resource. They are about finding the right balance between throughput, latency, fairness, and stability for your specific workload.</p>
<p>The post <a href="https://severalnines.com/blog/managing-clickhouse-resources-in-multi-tenant-environments/">Managing ClickHouse Resources in Multi-Tenant  Environments</a> appeared first on <a href="https://severalnines.com">Severalnines</a>.</p>

<p>The post <a rel="nofollow" href="https://severalnines.com/blog/managing-clickhouse-resources-in-multi-tenant-environments/">Managing ClickHouse Resources in Multi-Tenant  Environments</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Percona Operator for MySQL (PXC) 1.20.0: Automatic Storage Resizing, TLS Certificate Rotation, and ARM64 Support</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/percona-operator-for-mysql-pxc-1-20-0-automatic-storage-resizing-tls-rotation-arm64/" />
      <id>https://www.percona.com/blog/percona-operator-for-mysql-pxc-1-20-0-automatic-storage-resizing-tls-rotation-arm64/</id>
      <updated>2026-06-09T10:25:46+00:00</updated>
      <author><name>Slava Sarzhan</name></author>
      <summary type="html"><![CDATA[<p>Percona Operator for MySQL PXC 1.20.0 is out today, and it addresses three long-requested operational headaches: storage that grows on its own before it fills up, TLS certificates that rotate without cluster downtime, and images that run natively on ARM64. Disk-full incidents on PXC clusters often arrive at 2 AM when monitoring alerts fire, and … Continued<br />
The post Percona Operator for MySQL (PXC) 1.20.0: Automatic Storage Resizing, TLS Certificate Rotation, and ARM64 Support appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/percona-operator-for-mysql-pxc-1-20-0-automatic-storage-resizing-tls-rotation-arm64/">Percona Operator for MySQL (PXC) 1.20.0: Automatic Storage Resizing, TLS Certificate Rotation, and ARM64 Support</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><img decoding="async" loading="lazy" class="aligncenter wp-image-49249 size-large" src="https://www.percona.com/wp-content/uploads/2026/06/Hero-1-1024x376.png" alt="" width="1024" height="376"></p>
<p><span style="font-weight: 400">Percona Operator for MySQL PXC 1.20.0 is out today, and it addresses three long-requested operational headaches: storage that grows on its own before it fills up, TLS certificates that rotate without cluster downtime, and images that run natively on ARM64.</span></p>
<p><span style="font-weight: 400">Disk-full incidents on PXC clusters often arrive at 2 AM when monitoring alerts fire, and someone has to manually expand PVCs before writes grind to a halt. Certificate rotations have traditionally meant a carefully timed series of kubectl edits with real downtime risk. And ARM64 hardware has been increasingly common in dev clusters and cost-optimized cloud node pools, where x86-only images created extra friction. 1.20.0 addresses all three in a single release.</span></p>
<div data-line="17" data-line-type="change-addition" data-line-index="17,16">The operator is open source and runs on any CNCF-conformant Kubernetes distribution, including GKE, EKS, AKS, and OpenShift. <span data-diff-span="">It supports </span>Kubernetes 1.33 through 1.36 and PXC 8.4, 8.0, and 5.7.</div>
<p>&nbsp;</p>
<p><span style="font-weight: 400">In this post, you&rsquo;ll learn about:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Automatic PVC storage resizing with configurable thresholds and a hard cap</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Zero-downtime TLS certificate rotation via a new Secret naming convention</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Native ARM64 support across all operator images</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">PITR validation that catches misconfigured targets before restores begin</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Configurable leader election for high-latency or unstable networks</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Other improvements in this release</span></li>
</ul>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">Automatic Storage Resizing</span><a class="anchor-link" id="automatic-storage-resizing"></a></h2>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-49251 size-large" src="https://www.percona.com/wp-content/uploads/2026/06/storage-resizing-1024x563.png" alt="" width="1024" height="563"></p>
<p>&nbsp;</p>
<h3><span style="font-weight: 400">Why it matters</span><a class="anchor-link" id="why-it-matters"></a></h3>
<p><span style="font-weight: 400">A full data volume is the most common cause of unplanned maintenance on a PXC cluster. Until now, avoiding it required external monitoring, manual </span><span style="color: #ff6600"><i><span style="font-weight: 400">kubectl patch pvc</span></i></span><span style="font-weight: 400"> steps, and waiting for the storage class to honor the resize. Even with good alerting, the operator itself had no mechanism to react: it could only expand PVCs when you changed the spec by hand.</span></p>
<p>1.20.0 introduces built-in storage autoscaling. The operator polls each PVC&rsquo;s actual disk usage, and when usage crosses a configured threshold, it automatically expands the claim. You set the trigger percentage, the step size per resize event, and an optional upper bound. <span data-diff-span="">The operator handles everything else</span>.</p>
<p>&nbsp;</p>
<h3><span style="font-weight: 400">How it works</span><a class="anchor-link" id="how-it-works"></a></h3>
<p>The autoscaler runs inside the normal reconcile loop. It reads <em><span style="color: #ff6600">status.capacity.storage</span></em> from each PXC PVC, compares current usage against <em><span style="color: #ff6600">triggerThresholdPercent</span></em>, and issues a PVC resize when the threshold is crossed. <span data-diff-span="">It sets a </span><em><span style="color: #ff6600">percona.com/pvc-resize-in-progress</span></em> annotation on the CR while an expansion is active. This annotation blocks concurrent rolling restarts or upgrades from starting<span data-diff-span="">,</span> so <span data-diff-span="">nothing disrupts </span>the cluster mid-resize.</p>
<p>You can also set&nbsp;<em><span style="color: #ff6600">enableExternalAutoscaling: true</span></em><span style="color: #ff6600">&nbsp;</span>if an external tool, such as KEDA, already manages PVC sizes for your cluster.&nbsp;When <span data-diff-span="">you enable external autoscaling</span>, the built-in loop skips its resize check entirely to avoid conflicts.</p>
<p>&nbsp;</p>
<h3><span style="font-weight: 400">Wiring it up</span><a class="anchor-link" id="wiring-it-up"></a></h3>
<p><span style="font-weight: 400">Add </span><span style="color: #ff6600"><i><span style="font-weight: 400">storageScaling</span></i></span><span style="font-weight: 400"> to your </span><span style="color: #ff6600"><i><span style="font-weight: 400">PerconaXtraDBCluster</span></i></span><span style="font-weight: 400"> spec:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: pxc.percona.com/v1
kind: PerconaXtraDBCluster
metadata:
  name: cluster1
spec:
  crVersion: 1.20.0
  storageScaling:
    enableVolumeScaling: true
    autoscaling:
      enabled: true
      triggerThresholdPercent: 80   # resize when a PVC is 80% full
      growthStep: 2Gi               # add 2Gi per resize event
      maxSize: 100Gi                # never grow beyond 100Gi per PVC
#     enableExternalAutoscaling: false</pre>
<p><span data-diff-span="">Any </span>PVC expansion <span data-diff-span="">requires <span style="color: #ff6600"><em>enableVolumeScaling: true</em></span></span>, whether the autoscaler or a manual spec change<span data-diff-span=""> triggers it</span>. Setting <span style="color: #ff6600"><em>autoscaling.enabled: true</em></span> enables the threshold-based path on top of that. Leave the <em><span style="color: #ff6600">autoscaling</span></em> block out if you only want to permit manual spec-driven resizes.</p>
<p>&nbsp;</p>
<h3><span style="font-weight: 400">Caveats</span><a class="anchor-link" id="caveats"></a></h3>
<p><span style="font-weight: 400">Storage expansion requires a StorageClass with </span><span style="color: #ff6600"><i><span style="font-weight: 400">allowVolumeExpansion: true</span></i><i><span style="font-weight: 400">.</span></i></span><span style="font-weight: 400"> Check before enabling:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl get storageclass 
  -o jsonpath='{range .items[*]}{.metadata.name}{"t"}{.allowVolumeExpansion}{"n"}{end}'</pre>
<p><span style="font-weight: 400">Autoscaling applies only to PXC data volumes. If your storage class or CSI driver handles expansion externally, use </span><span style="color: #ff6600"><i><span style="font-weight: 400">enableExternalAutoscaling: true</span></i></span><span style="font-weight: 400"> to prevent the two mechanisms from racing.</span></p>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">Automated TLS Certificate Rotation</span><a class="anchor-link" id="automated-tls-certificate-rotation"></a></h2>
<h3><span style="font-weight: 400">Why it matters</span><a class="anchor-link" id="why-it-matters"></a></h3>
<p><span style="font-weight: 400">Rotating TLS certificates on a live PXC cluster has always carried risk. The Galera protocol requires all nodes to trust each other&rsquo;s CA simultaneously. Swap the CA on one node before the others accept it, and inter-node communication breaks. The safe approach requires a three-phase CA swap with rolling restarts between each phase: a process that is easy to get wrong under time pressure.</span></p>
<p><span style="font-weight: 400">1.20.0 formalizes this into a first-class operator workflow. Create a Secret named </span><span style="color: #ff6600"><i><span style="font-weight: 400">&lt;ssl-secret&gt;-new </span></i></span><span style="font-weight: 400">containing the replacement credentials, and the operator runs the full three-phase rotation automatically, pausing for rolling restarts between each step.</span></p>
<p>&nbsp;</p>
<h3><span style="font-weight: 400">How it works</span><a class="anchor-link" id="how-it-works"></a></h3>
<p>The rotation proceeds in three steps <span data-diff-span="">that </span>the operator<span data-diff-span=""> coordinates</span>:</p>
<ol>
<li style="font-weight: 400"><b>Combined CA phase</b><span style="font-weight: 400">. The old CA and new CA are merged into a single </span><span style="color: #ff6600"><i><span style="font-weight: 400">ca.crt</span></i></span><span style="font-weight: 400"> and pushed to all nodes. Every node now trusts both roots.</span></li>
<li style="font-weight: 400"><b>New leaf phase.</b><span style="font-weight: 400"> The new </span><span style="color: #ff6600"><i><span style="font-weight: 400">tls.crt</span></i></span><span style="font-weight: 400"> and</span><span style="color: #ff6600"><i><span style="font-weight: 400"> tls.key</span></i></span><span style="font-weight: 400"> are pushed node by node with a rolling restart. New leaf certs are signed by the new CA, and the combined CA means all nodes trust them.</span></li>
<li style="font-weight: 400"><b>New CA only phase.</b><span style="font-weight: 400"> The combined </span><span style="color: #ff6600"><i><span style="font-weight: 400">ca.crt</span></i></span><span style="font-weight: 400"> is replaced with the new CA only. The old root is removed. Another rolling restart completes the rotation.</span></li>
</ol>
<p><span style="font-weight: 400">When step 3 completes, the operator automatically deletes the </span><span style="color: #ff6600"><i><span style="font-weight: 400">-new</span></i></span><span style="font-weight: 400"> Secret. The cluster never loses TLS connectivity between nodes during the process.</span></p>
<p>&nbsp;</p>
<h3><span style="font-weight: 400">Wiring it up</span><a class="anchor-link" id="wiring-it-up"></a></h3>
<p><span style="font-weight: 400">Given a cluster named </span><span style="color: #ff6600"><i><span style="font-weight: 400">cluster1 </span></i></span><span style="font-weight: 400">using the default SSL Secret </span><span style="color: #ff6600"><i><span style="font-weight: 400">cluster1-ssl</span></i></span><span style="font-weight: 400">, create the replacement:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl create secret generic cluster1-ssl-new 
  --from-file=ca.crt=new-ca.crt 
  --from-file=tls.crt=new-server.crt 
  --from-file=tls.key=new-server.key</pre>
<p><span data-diff-span="">You do not need </span>to <span data-diff-span="">change </span>the <span style="color: #ff6600"><em>PerconaXtraDBCluster</em></span> CR. The operator detects the <em><span style="color: #ff6600">-new</span></em> Secret on the next reconcile and starts the rotation. No <span style="color: #ff6600"><em>kubectl patch</em></span> on the CR, no operator restart.</p>
<p>&nbsp;</p>
<h3><span style="font-weight: 400">Caveats</span><a class="anchor-link" id="caveats"></a></h3>
<p><span style="font-weight: 400">The operator does not yet surface rotation progress in</span><span style="color: #ff6600"><i><span style="font-weight: 400"> .status.conditions</span></i></span><span style="font-weight: 400">. Monitor the rotation by watching PXC pods restart in sequence and checking that the </span><span style="color: #ff6600"><i><span style="font-weight: 400">-new </span></i></span><span style="font-weight: 400">Secret is eventually gone:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl get pods -w -l app.kubernetes.io/component=pxc
kubectl get secret cluster1-ssl-new  # should 404 when rotation is complete</pre>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">ARM64 Support</span><a class="anchor-link" id="arm64-support"></a></h2>
<p>&nbsp;</p>
<h3><span style="font-weight: 400">Why it matters</span><a class="anchor-link" id="why-it-matters"></a></h3>
<p><span style="font-weight: 400">AWS Graviton3, Google Axion, and Azure Cobalt100 instances deliver better price-to-performance on memory-intensive workloads like PXC. Previously, running the operator on ARM64 nodes required cross-architecture scheduling workarounds or explicit node exclusions for operator pods. All PXC operator images now publish native </span><span style="color: #ff6600"><i><span style="font-weight: 400">linux/arm64 </span></i></span><span style="font-weight: 400">layers alongside </span><span style="color: #ff6600"><i><span style="font-weight: 400">nodeSelector<br>
</span></i></span></p>
<p>&nbsp;</p>
<h3><span style="font-weight: 400">What is covered</span><a class="anchor-link" id="what-is-covered"></a></h3>
<p><span style="font-weight: 400">Every image in the PXC operator stack ships multi-arch manifests in 1.20.0:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The operator manager image</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The PXC xtrabackup sidecar</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The log collector (Fluentbit-based)</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The init container</span></li>
</ul>
<p>This release also fixes a logrotate crash on ARM64 (<a href="https://perconadev.atlassian.net/browse/K8SPXC-1821">K8SPXC-1821</a>) <span data-diff-span="">that </span>a missing dependency in the ARM64 container layer<span data-diff-span=""> caused</span>. 1.20.0<span data-diff-span=""> ships the fix</span>.<br>
&nbsp;</p>
<h3><span style="font-weight: 400">Wiring it up</span><a class="anchor-link" id="wiring-it-up"></a></h3>
<p><span data-diff-span="">You do not need any configuration change</span>. Pull the 1.20.0 operator image and Kubernetes schedules it on whichever architecture is available. To pin PXC pods explicitly to ARM64 nodes, add a <span style="color: #ff6600"><em>nodeSelector</em></span> or node affinity in the <em><span style="color: #ff6600">spec.pxc</span></em> block:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">spec:
  pxc:
    nodeSelector:
      kubernetes.io/arch: arm64</pre>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">Other Improvements</span><a class="anchor-link" id="other-improvements"></a></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">PITR target validation before restore begins (</span><a href="https://perconadev.atlassian.net/browse/K8SPXC-1318"><span style="font-weight: 400">K8SPXC-1318</span></a><span style="font-weight: 400">, </span><a href="https://perconadev.atlassian.net/browse/K8SPXC-1634"><span style="font-weight: 400">K8SPXC-1634</span></a><span style="font-weight: 400">, </span><a href="https://perconadev.atlassian.net/browse/K8SPXC-1635"><span style="font-weight: 400">K8SPXC-1635</span></a><span style="font-weight: 400">, </span><a href="https://perconadev.atlassian.net/browse/K8SPXC-1793"><span style="font-weight: 400">K8SPXC-1793</span></a><span style="font-weight: 400">): The operator now validates PITR targets (type, GTID, timestamp) against available binary logs before starting a restore. <span data-diff-span="">It catches a </span>misconfigured target <span data-diff-span="">before it pauses</span> the cluster, rather than after.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Configurable leader election (</span><a href="https://perconadev.atlassian.net/browse/K8SPXC-1805"><span style="font-weight: 400">K8SPXC-1805</span></a><span style="font-weight: 400">): Three new environment variables tune leader election timing for high-latency or flaky network environments.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">SST retry limit (</span><a href="https://perconadev.atlassian.net/browse/K8SPXC-1619"><span style="font-weight: 400">K8SPXC-1619</span></a><span style="font-weight: 400">): A new </span><span style="color: #ff6600"><i><span style="font-weight: 400">spec.pxc.sstRetryCount</span></i></span><span style="font-weight: 400"> field caps the number of State Snapshot Transfer retry attempts, preventing a node that repeatedly fails SST from looping indefinitely.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Custom logrotate configuration (</span><a href="https://perconadev.atlassian.net/browse/K8SPXC-1789"><span style="font-weight: 400">K8SPXC-1789</span></a><span style="font-weight: 400">): Supply a custom logrotate config via a ConfigMap reference in </span><span style="color: #ff6600"><i><span style="font-weight: 400">spec.logcollector.logRotate </span></i></span><span style="font-weight: 400">for fine-grained control over log rotation for PXC and utility containers.</span></li>
<li>Enhanced full cluster crash recovery&nbsp;(<a class="text-[var(--accent)] hover:underline underline-offset-[1px] outline-none hide-focus-ring ring-focus rounded-r2" href="https://perconadev.atlassian.net/browse/K8SPXC-1828" target="_blank" rel="noopener noreferrer">K8SPXC-1828</a>): 1.20.0 hardens the crash recovery path to prevent potential data loss after sudden node power-offs.</li>
</ul>
<p>&nbsp;</p>
<blockquote>
<p><b><i>Deprecation notice:</i></b><i><span style="font-weight: 400"> PMM2 monitoring integration is deprecated in 1.20.0. Migrate to PMM 3 before version 1.22.0, when PMM2 support will be removed.</span></i></p>
</blockquote>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">Conclusion</span><a class="anchor-link" id="conclusion"></a></h2>
<p><span style="font-weight: 400">PXC Operator 1.20.0 turns three previously manual steps into operator-managed concerns: disk growth, certificate rotation, and ARM64 scheduling. Combined with PITR validation improvements and configurable leader election, this release reduces the operational surface area for clusters running under production pressure. If you run into edge cases with automatic storage resizing or TLS rotation, the community forum is the right place to share them.</span></p>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">Try It Out</span><a class="anchor-link" id="try-it-out"></a></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Release notes: </span><a href="https://docs.percona.com/percona-operator-for-mysql/pxc/ReleaseNotes/Kubernetes-Operator-for-PXC-RN1.20.0.html"><span style="font-weight: 400">Percona Operator for MySQL (PXC) 1.20.0 Release Notes</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">GitHub: </span><a href="https://github.com/percona/percona-xtradb-cluster-operator"><span style="font-weight: 400">percona/percona-xtradb-cluster-operator</span></a></li>
<li style="font-weight: 400"><span style="font-weight: 400">Public roadmap: </span><a href="https://github.com/orgs/percona/projects/10/views/5"><span style="font-weight: 400">Percona public roadmap</span></a><span style="font-weight: 400">. See what is coming and vote on priorities.</span></li>
<li style="font-weight: 400">Community Forum: <a href="https://forums.percona.com/">forums.percona.com</a>. Share feedback, ask questions, or report issues.</li>
</ul>
<p>&nbsp;</p>
<p>The post <a href="https://www.percona.com/blog/percona-operator-for-mysql-pxc-1-20-0-automatic-storage-resizing-tls-rotation-arm64/">Percona Operator for MySQL (PXC) 1.20.0: Automatic Storage Resizing, TLS Certificate Rotation, and ARM64 Support</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/percona-operator-for-mysql-pxc-1-20-0-automatic-storage-resizing-tls-rotation-arm64/">Percona Operator for MySQL (PXC) 1.20.0: Automatic Storage Resizing, TLS Certificate Rotation, and ARM64 Support</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Foundation Sea Lion Champions Nominees: Mark Callaghan</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-mark-callaghan/" />
      <id>https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-mark-callaghan/</id>
      <updated>2026-06-09T05:55:04+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>Interview with Mark Callaghan, nominated in the Technical Excellence category.<br />
I had the pleasure of speaking with Mark Callaghan, recently nominated for the MariaDB Sea Lion Champions program in the “Technical Excellence” …<br />
Continue reading \"MariaDB Foundation Sea Lion Champions Nominees: Mark Callaghan\"<br />
The post MariaDB Foundation Sea Lion Champions Nominees: Mark Callaghan appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-mark-callaghan/">MariaDB Foundation Sea Lion Champions Nominees: Mark Callaghan</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Interview with Mark Callaghan, nominated in the Technical Excellence category.<br>
I had the pleasure of speaking with Mark Callaghan, recently nominated for the MariaDB Sea Lion Champions program in the &ldquo;Technical Excellence&rdquo; &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-mark-callaghan/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Foundation Sea Lion Champions Nominees: Mark Callaghan&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-mark-callaghan/">MariaDB Foundation Sea Lion Champions Nominees: Mark Callaghan</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-mark-callaghan/">MariaDB Foundation Sea Lion Champions Nominees: Mark Callaghan</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Foundation Sea Lion Champions Nominees: Sumit Srivastava</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-sumit-srivastava/" />
      <id>https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-sumit-srivastava/</id>
      <updated>2026-06-08T08:41:43+00:00</updated>
      <author><name>Kaj Arnö</name></author>
      <summary type="html"><![CDATA[<p>Interview with Sumit Srivastava, nominated in the Adoption &#038; Industry Impact category.<br />
I had the pleasure of speaking with Sumit Srivastava, SVP Business Development &#038; Products at Tayana, a Bangalore-based telecom software company. …<br />
Continue reading \"MariaDB Foundation Sea Lion Champions Nominees: Sumit Srivastava\"<br />
The post MariaDB Foundation Sea Lion Champions Nominees: Sumit Srivastava appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-sumit-srivastava/">MariaDB Foundation Sea Lion Champions Nominees: Sumit Srivastava</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Interview with Sumit Srivastava, nominated in the Adoption &amp; Industry Impact category.<br>
I had the pleasure of speaking with Sumit Srivastava, SVP Business Development &amp; Products at Tayana, a Bangalore-based telecom software company. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-sumit-srivastava/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Foundation Sea Lion Champions Nominees: Sumit Srivastava&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-sumit-srivastava/">MariaDB Foundation Sea Lion Champions Nominees: Sumit Srivastava</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-sumit-srivastava/">MariaDB Foundation Sea Lion Champions Nominees: Sumit Srivastava</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>The Power Of The Community!</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/the-power-of-the-community/" />
      <id>https://mariadb.org/the-power-of-the-community/</id>
      <updated>2026-06-04T11:42:04+00:00</updated>
      <author><name>Georgi Kodinov</name></author>
      <summary type="html"><![CDATA[<p>A proper comparison of the distinct committers to MariaDB and MySQL repositories since Q1 2025.<br />
The post The Power Of The Community! appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/the-power-of-the-community/">The Power Of The Community!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>A proper comparison of the distinct committers to MariaDB and MySQL repositories since Q1 2025.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/the-power-of-the-community/">The Power Of The Community!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/the-power-of-the-community/">The Power Of The Community!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Why Modern Finance Runs on Open Source</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/why-modern-finance-runs-on-open-source/" />
      <id>https://www.percona.com/blog/why-modern-finance-runs-on-open-source/</id>
      <updated>2026-06-04T10:59:30+00:00</updated>
      <author><name>Scott LaFortune</name></author>
      <summary type="html"><![CDATA[<p>For decades, financial institutions have relied on proprietary databases to power everything from customer transactions to real-time risk engines. But the pressures facing banks, fintechs, and payment providers have changed. Even minutes of downtime can trigger customer loss. 91% of enterprises report costs over $300,000 per hour, with 44% saying it can exceed $1 million. … Continued<br />
The post Why Modern Finance Runs on Open Source appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/why-modern-finance-runs-on-open-source/">Why Modern Finance Runs on Open Source</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>For decades, financial institutions have relied on proprietary databases to power everything from customer transactions to real-time risk engines. But the pressures facing banks, fintechs, and payment providers have changed.</p>
<p>Even minutes of downtime can trigger customer loss. <a href="https://itic-corp.com/tag/hourly-cost-of-downtime/">91% of enterprises</a> report costs over $300,000 per hour, with 44% saying it can exceed $1 million.</p>
<p>At this level of operational risk, databases must scale predictably and adapt quickly. Proprietary databases often work against that goal. Escalating licensing fees, usage-based pricing, and long-term vendor lock-in make it harder to tune performance, expand capacity, or respond to incidents without compounding cost and complexity.</p>
<p>This is why open-source databases have become a core part of modernization efforts across banks, fintechs, and payment platforms. They help keep costs predictable as performance, security, and regulatory demands continue to rise.</p>
<h2>The Financial Industry Is Under Pressure Like Never Before<a class="anchor-link" id="the-financial-industry-is-under-pressure-like-never-before"></a></h2>
<p>Financial institutions are facing unprecedented pressure, putting legacy systems under strain and impacting performance, costs, and compliance.</p>
<h3>Regulatory Expectations Keep Rising<a class="anchor-link" id="regulatory-expectations-keep-rising"></a></h3>
<p>Rising regulatory expectations demand continuous auditing and compliance with PCI DSS, GDPR, AML/ATF, and ESG standards. Complete transparency, high security, and verifiable controls are no longer negotiable.</p>
<p>The proprietary databases introduce friction, since closed architectures and licensing controls restrict the disclosure that regulators require.</p>
<h3>Customers Expect Real-Time Performance<a class="anchor-link" id="customers-expect-real-time-performance"></a></h3>
<p>Even brief transaction delays or outages trigger immediate loss of trust, especially as fintechs set the benchmark for instant, failure-proof experiences. Traditional release cycles and weekend maintenance windows no longer match customer tolerance.</p>
<h3>AI and Data Growth Exceed Traditional Limits<a class="anchor-link" id="ai-and-data-growth-exceed-traditional-limits"></a></h3>
<p>AI workloads and high-dimensional data pipelines drive unprecedented data volume, outpacing the capacity of legacy systems. The rising pressure is translating into growing data center spending, which is predicted to reach <a href="https://www.networkworld.com/article/3820973/data-center-spending-to-top-1-trillion-by-2029-as-ai-transforms-infrastructure.html">$1 trillion</a> in 2029, largely driven by these workloads.</p>
<h3>Legacy and Proprietary Systems Stall Modernization<a class="anchor-link" id="legacy-and-proprietary-systems-stall-modernization"></a></h3>
<p>Vendor-controlled databases slow deployment pipelines, restrict architectural flexibility, and inflate Total Cost of Ownership (TCO) as estates grow more fragmented. Teams lose velocity as they navigate outdated scaling constraints, rigid licensing, and operational silos.</p>
<h2>The Real Constraint: Proprietary Databases and Vendor Lock-In<a class="anchor-link" id="the-real-constraint-proprietary-databases-and-vendor-lock-in"></a></h2>
<p>Even as financial institutions modernize, proprietary databases remain a source of costly, rigid constraints that hinder real progress. Let&rsquo;s see how these constraints surface in practice.</p>
<h3>Licensing Costs that Rise Faster than Value<a class="anchor-link" id="licensing-costs-that-rise-faster-than-value"></a></h3>
<p>Proprietary licensing has quietly become a structural tax on modernization.</p>
<ul>
<li>Rising license fees without added capability directly slow innovation.</li>
<li>Compliance-critical features like encryption, auditing, and high availability (HA) are locked behind premium tiers, driving TCO far above infrastructure costs.</li>
</ul>
<p>When Redis shifted to a more restrictive license, the market reaction was immediate. <a href="https://www.percona.com/blog/redis-users-want-a-change/">Nearly 75% of Redis users began exploring alternatives</a>, and over three-quarters were already testing or migrating to new options.</p>
<p>For financial institutions with regulatory, uptime, and AI requirements, this goes beyond cost and starts to limit long-term decisions.</p>
<h3>Features Locked Behind Enterprise Tiers<a class="anchor-link" id="features-locked-behind-enterprise-tiers"></a></h3>
<p>Compliance and security are typically offered as premium add-ons rather than standard features. Multi-layer encryption, detailed auditing, enterprise HA, and reliable backup may require costly upgrades. Maintaining baseline security and regulatory standards can drive unexpected spending.</p>
<p>In contrast, modern open-source databases come with many of these features built in or easily added, giving institutions more control instead of leaving it with the vendor.</p>
<h3>Forced Upgrades and Migration Penalties<a class="anchor-link" id="forced-upgrades-and-migration-penalties"></a></h3>
<p>Closed licensing gives vendors control over upgrade timing and support windows, not the institutions that run the risk. Forced version jumps and commercial changes rarely align with project roadmaps or risk calendars.</p>
<p>In financial services, where planned change is itself a control, this loss of autonomy introduces unnecessary operational and regulatory exposure.</p>
<h3>Limited Portability Across Hybrid and Multi-Cloud<a class="anchor-link" id="limited-portability-across-hybrid-and-multi-cloud"></a></h3>
<p><a href="https://www.sciencedirect.com/science/article/abs/pii/S0167404825002883">Hybrid and multi-cloud</a> have become standard operating models in finance. But proprietary engines often resist movement across regions, providers, or Kubernetes platforms.</p>
<p>That immobility limits options for latency optimization, data sovereignty, and disaster recovery patterns, exactly where many institutions need the most freedom.</p>
<h3>DBaaS Convenience That Turns Into Runaway Spend<a class="anchor-link" id="dbaas-convenience-that-turns-into-runaway-spend"></a></h3>
<p>A managed proprietary DBaaS looks attractive at first. However, at scale, the economics often flip. Percona&rsquo;s analysis shows that over <a href="https://www.percona.com/blog/alternatives-to-cloud-dbaas-what-to-look-for/">74%</a> of DBaaS users cite high and unpredictable costs as their top challenge, a direct result of opaque, usage-based pricing models.</p>
<p>The institutions encounter uncertain costs on IOPS, storage, backups, cross-region traffic, and autoscaling decisions that they have no full control over.</p>
<p>Financial institutions should not rent out their data infrastructure on conditions that may shift unexpectedly to remain competitive. That need for control, portability, and predictability is what&rsquo;s driving the industry toward open source.</p>
<h2>Why Open Source Has Become the Strategic Advantage in Finance<a class="anchor-link" id="why-open-source-has-become-the-strategic-advantage-in-finance"></a></h2>
<p>With the global open source database market projected to reach USD 63.48 billion by 2034, more and more financial institutions are recognizing its strategic value. Open source now gives them the control, transparency, and flexibility that proprietary databases can&rsquo;t match.</p>
<p><img decoding="async" loading="lazy" class="alignnone wp-image-48689 size-full" src="https://www.percona.com/wp-content/uploads/2026/06/Open-Source-Database-Market.png" alt="Open-Source-Database-Market" width="1291" height="805"></p>
<p style="text-align: center">Open Source Database Market Size | <a href="https://market.us/report/open-source-database-market/"><span style="font-weight: 400">Source</span></a></p>
<p>Let&rsquo;s look at why open source now leads in every dimension that matters.</p>
<h3>Cost Control with Predictable Economics<a class="anchor-link" id="cost-control-with-predictable-economics"></a></h3>
<p>The shift to open source begins with transparency. Where proprietary vendors raise licensing fees and monetize basic compliance features, open source removes artificial cost ceilings and aligns spend with real usage. This includes:</p>
<ul>
<li>No per-core or edition-based licensing</li>
<li>No penalties for scaling read replicas or adding environments</li>
<li>No enterprise tier required for auditing, encryption, or HA</li>
</ul>
<p>Open source restores <a href="https://learn.percona.com/hubfs/eBooks/Fixing-Data-Slowdowns-Without-Breaking-the-Bank.pdf">financial governance</a> and eliminates unexpected cost shocks as data volumes grow. This gives organizations predictable economics and control over their database spending.</p>
<h3>Scalability Without Constraint<a class="anchor-link" id="scalability-without-constraint"></a></h3>
<p>Open source scales with <a href="https://www.percona.com/blog/open-source-vs-proprietary-software">demand</a>, not with licensing policy. In high-volume trading or high payment flow periods, capacity can grow vertically or horizontally without precipitating premium editions, replica fees, or per-core uplifts.</p>
<p>Cloud-native automation strengthens elasticity across regions, clouds, and Kubernetes environments. It enables scale-out patterns that follow real workload behavior instead of vendor-driven architecture choices.</p>
<p>This saves the IOPS taxes, network surcharges, and hard-and-fast capacity levels that often bloat proprietary DBaaS prices.</p>
<h3>Security and Compliance Through Transparency<a class="anchor-link" id="security-and-compliance-through-transparency"></a></h3>
<p>Financial institutions need to demonstrate every control they implement. Proprietary databases make this challenging because their security mechanisms cannot be inspected or validated.</p>
<p>Open source removes this barrier by giving teams full visibility into how security controls and safeguards function. That clarity sets the foundation for the capabilities that follow:</p>
<ul>
<li>Native encryption at rest, in transit, and at granular data levels</li>
<li>Robust role-based access control (RBAC) and deep auditing frameworks, such as <a href="https://www.pgaudit.org/">pgAudit</a></li>
<li>Configurable policies mapped to PCI DSS, GDPR, AML/ATF, and ESG standards</li>
</ul>
<p>As regulatory pressure accelerates, this transparency becomes essential. Institutions strengthen compliance, reduce licensing costs, and avoid security features locked behind enterprise editions.</p>
<h3>High Availability and Uptime You Can Design, Not Inherit<a class="anchor-link" id="high-availability-and-uptime-you-can-design-not-inherit"></a></h3>
<p>Financial institutions cannot tolerate unpredictable failover behavior. Open source gives architects full control over HA/DR strategy, rather than relying on opaque vendor-managed mechanisms. This includes:</p>
<ul>
<li>Synchronous and asynchronous replication</li>
<li>Multi-region and multi&ndash;data center architectures</li>
<li>Automated failover without black-box dependencies</li>
<li>The ability to tune HA for latency, cost, or regulatory constraints</li>
</ul>
<p>HA is not a feature to buy but an architecture you can design with open source.</p>
<h3>Modernization and Innovation at Enterprise Scale<a class="anchor-link" id="modernization-and-innovation-at-enterprise-scale"></a></h3>
<p>Modern platforms require modular, API driven, cloud native systems. Open source databases integrate naturally into:</p>
<ul>
<li>Cloud-native automation and Kubernetes</li>
<li>CI/CD pipelines to deliver features faster</li>
<li>Asynchronous architectures</li>
<li>Telemetry and ML streams</li>
</ul>
<p>Proprietary databases slow modernization by restricting portability, enforcing rigid architectures, and complicating automation. Open source removes these barriers and gives teams the freedom to experiment and move quickly.</p>
<h3>Talent and Ecosystem Alignment<a class="anchor-link" id="talent-and-ecosystem-alignment"></a></h3>
<p>Open source tooling is widely embraced by developers, SREs, architects, and data engineers, as it aligns seamlessly with how modern teams build, test, and scale software. This includes:</p>
<ul>
<li>Larger talent pools for PostgreSQL, MySQL, MongoDB, and cloud-native ecosystems</li>
<li>Faster internal development cycles</li>
<li>Higher retention and a more collaborative engineering culture</li>
</ul>
<p>Open source isn&rsquo;t simply a cheaper alternative, it is structurally aligned with how modern financial institutions operate. It provides the control, visibility, scalability, and freedom needed to compete in a market of data, AI, compliance, and relentless uptime requirements.</p>
<h2>Making Open Source Enterprise-Grade: Where Percona Fits In<a class="anchor-link" id="making-open-source-enterprise-grade-where-percona-fits-in"></a></h2>
<p>Open source provides financial institutions with flexibility and cost control. <a href="https://docs.percona.com/">Percona</a> turns that foundation into an operationally mature, secure, high-availability data platform suitable for regulated, always-on financial workloads.</p>
<h3>Unified Multi-Database Expertise<a class="anchor-link" id="unified-multi-database-expertise"></a></h3>
<p>Financial systems commonly use multiple data engines, MySQL for transactions, PostgreSQL for analytics, MongoDB for document storage, and more. Percona supports this diversity through a single engineering organization, offering:</p>
<ul>
<li>Full production&#8209;grade support for MySQL, PostgreSQL, and MongoDB via <a href="https://www.percona.com/software/percona-operators">Percona&rsquo;s distributions</a>.</li>
<li>Cross&#8209;engine expertise in performance, indexing, replication, and <a href="https://docs.percona.com/percona-toolkit/pt-online-schema-change.html">schema</a> strategies.</li>
<li>An integrable observability layer through <a href="https://experience.percona.com/postgresql/monitoring-and-management/">Percona Monitoring and Management (PMM)</a>, with the ability to monitor and control all your supported engines on a single dashboard.</li>
</ul>
<p>This unified approach gives financial institutions a consistent, reliable way to operate diverse database environments at scale.</p>
<h3>Enterprise-Ready Open Source Software<a class="anchor-link" id="enterprise-ready-open-source-software"></a></h3>
<p>Percona&rsquo;s database distributions take community editions and enhance them with features and defaults tuned for enterprise production:</p>
<ul>
<li><a href="https://www.percona.com/postgresql">Percona&rsquo;s PostgreSQL distribution</a> offers enterprise&#8209;ready security and high availability with no licensing fees or hidden add-ons.</li>
<li>Performance optimizations and configuration tuning beyond stock community defaults.</li>
<li>High&#8209;availability automation, backup and recovery workflows (including point-in-time recovery), replication/sharding, and automated cluster scaling.</li>
</ul>
<p>This means financial teams can run open&#8209;source databases with the predictability, operational rigor, and enterprise-grade features often associated with commercial editions.</p>
<h3>24&times;7&times;365 Support Built for Critical Financial System<a class="anchor-link" id="24x7x365-support-built-for-critical-financial-system"></a></h3>
<p>Percona keeps your databases online:</p>
<ul>
<li><a href="https://www.percona.com/software/percona-operators">Operators automate</a> backups, scaling, upgrades, and HA.</li>
<li>PMM offers a single-source monitoring, alerting, and management of MySQL, PostgreSQL, or MongoDB, on-prem, in the cloud, or both.</li>
<li>Live <a href="https://www.percona.com/services/support">tracking and assistance</a> during trading periods, settlement hours, or compliance timelines.</li>
</ul>
<h3>Security and Operational Governance<a class="anchor-link" id="security-and-operational-governance"></a></h3>
<p>Percona ensures open source works within your compliance framework:</p>
<ul>
<li>Traceability (MongoDB, MySQL) through audit logging.</li>
<li>Data encryption in transit and at rest.</li>
<li>Fixed configuration defaults that are consistent with the industry best practices.</li>
<li>Recommendations on how to combine database security with <a href="https://docs.percona.com/percona-server-for-mongodb/6.0/aws-iam.html">identity access management (IAM)</a> and security information and event management (SIEM) systems.</li>
</ul>
<h3>Modernization and Vendor-Exit Expertise<a class="anchor-link" id="modernization-and-vendor-exit-expertise"></a></h3>
<p>Percona helps organizations modernize by migrating from proprietary databases to fully open-source solutions with minimal disruption:</p>
<ul>
<li>Complete open-source using MySQL, <a href="https://experience.percona.com/postgresql/enterprise-postgresql-buyers-guide/3-paying-a-premium-for-security-features-postgresql-includes-for-free">PostgreSQL</a>, and MongoDB.</li>
<li>On-prem, cloud, hybrid, and multi-cloud elastic deployments.</li>
<li>Maintain control over cost, compliance, and performance without lock-in to vendors.</li>
</ul>
<h2>Proof in Action: Financial Institutions Winning With Open Source<a class="anchor-link" id="proof-in-action-financial-institutions-winning-with-open-source"></a></h2>
<p>Modern financial institutions are already succeeding with open-source data infrastructures built and supported by Percona. Their results show what&rsquo;s possible when open-source databases are engineered, observed, and operated at enterprise scale.</p>
<ul>
<li><strong>Payments platforms:</strong> <a href="https://www.percona.com/customer-story/merchant-warrior-counts-on-percona-for-critical-availability/">Merchant Warrior</a> uses Percona for critical MySQL availability and resilience, supporting millions of transactions across 30,000+ customers. Percona XtraDB Cluster and PMM enable the team to have real-time insights and enterprise-level performance without downtime.</li>
<li><strong>Fintech and trading platforms:</strong> Fiserv implemented hybrid MongoDB, MySQL, and PostgreSQL clusters using Percona with ultra-low latency and performance of microseconds on key loads. This shows the efficiency of open-source software in helping financial platforms scale and maintain rigorous SLAs.</li>
<li><strong>Credit unions / digital banks:</strong> With Percona Server, <a href="https://www.percona.com/customer-story/bbva-migrates-document-oriented-database-nosql-workloads-to-percona-avoiding-license-costs-and-lock-in/">BBVA</a> moved over 80 applications and 35TB of MongoDB data. This reduced license fees, improved backup performance by 20%, and gave full control over its enterprise database strategy.</li>
<li><strong>Critical applications and cost savings:</strong> Protectall reduced escalating database costs and improved reliability by migrating from MySQL Enterprise to Percona Server. The team then partnered with Percona again to plan a long-term PostgreSQL migration that supports future growth.</li>
</ul>
<h2>What&rsquo;s Next: Take Control With Open Source<a class="anchor-link" id="whats-next-take-control-with-open-source"></a></h2>
<p>Open source is now the backbone of modern financial infrastructure. When your organization is struggling with increasing costs, modernization pressure, compliance problems, or vendor lock-in, it is time to take charge.</p>
<p>Upgrade your databases with <a href="https://www.percona.com/">Percona</a>, achieve enterprise-grade support, predictable costs, and operational control with <a href="https://www.percona.com/mysql/software">MySQL</a>, <a href="https://www.percona.com/postgresql/software/">PostgreSQL</a>, and <a href="https://www.percona.com/mongodb/software/">MongoDB</a>.</p>
<p>The future of finance is open source. Discover how institutions are reducing costs, ensuring compliance, and driving innovation.</p>
<p>The post <a href="https://www.percona.com/blog/why-modern-finance-runs-on-open-source/">Why Modern Finance Runs on Open Source</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/why-modern-finance-runs-on-open-source/">Why Modern Finance Runs on Open Source</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>What You’re Really Paying for With Proprietary Databases</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/what-youre-really-paying-for-with-proprietary-databases/" />
      <id>https://www.percona.com/blog/what-youre-really-paying-for-with-proprietary-databases/</id>
      <updated>2026-06-04T10:58:39+00:00</updated>
      <author><name>Scott LaFortune</name></author>
      <summary type="html"><![CDATA[<p>On paper, proprietary database licensing looks simple. You look at a predictable per-core licensing cost, the estimated infrastructure spend, and the support contract. But the actual cost of a proprietary database is not a line item. The predictability dissolves once workloads grow, new services are launched, or regulatory and uptime demands force you to scale … Continued<br />
The post What You’re Really Paying for With Proprietary Databases appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/what-youre-really-paying-for-with-proprietary-databases/">What You’re Really Paying for With Proprietary Databases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>On paper, proprietary database licensing looks simple. You look at a predictable per-core licensing cost, the estimated infrastructure spend, and the support contract.</p>
<p>But the actual cost of a proprietary database is not a line item. The predictability dissolves once workloads grow, new services are launched, or regulatory and uptime demands force you to scale aggressively. The bill shows up later as add-ons, markups, and architectural limitations that quietly shape every strategic decision.</p>
<p>Proprietary databases are rarely evaluated on the total <a href="https://learn.percona.com/cp-enterprise-tco-reduce-your-total-cost-of-ownership">cost of ownership</a> across five to ten years. Instead, they are justified as a necessary platform cost, even as their pricing, restrictions, and lock-in reduce your ability to modernize, migrate to the cloud, or optimize for AI and new products. Actually, you don&rsquo;t just pay for the database. You pay for the way it constrains your options.</p>
<p>This article explains the Total Cost of Ownership (TCO) of proprietary databases, detailing the architectural, operational, and strategic burdens they impose beyond the initial price.</p>
<h2>License fees are just the entry price<a class="anchor-link" id="license-fees-are-just-the-entry-price"></a></h2>
<p>Most proprietary databases start with a base license model, per-core, per-processor, per-socket, or tied to specific instance sizes and regions in the cloud.</p>
<p>As soon as you scale out (more nodes, more replicas, more environments) or scale up (more powerful instances), your licensing footprint expands, even if business value grows more slowly than technical capacity.</p>
<p>Tiered pricing forces expensive upgrades for critical features, such as <a href="https://www.percona.com/blog/transparent-data-encryption-tde/">Transparent Data Encryption (TDE)</a>, granular <a href="https://www.percona.com/sites/default/files/ple19-slides/ple19-PCI-DSS-Compliance-with-MySQL-2019-Edition.pdf">PCI-DSS</a> auditing, or <a href="https://www.percona.com/blog/the-ultimate-guide-to-database-high-availability/">active-active High Availability (HA)</a>.</p>
<p>Cloud database services add another layer by bundling license, compute, storage, and management into opaque service tiers with markups that can exceed <a href="https://www.percona.com/blog/managed-database-vs-kubernetes-taking-back-control-of-your-cloud-costs-and-agility/">80&ndash;100%</a> of the underlying infrastructure cost.</p>
<p>What looked like simple all-in pricing at proof of concept becomes a permanent premium as you scale across regions, add failover zones, and support 24&times;7 workloads.</p>
<p>Put simply, the license is just the entry ticket; the real spending kicks in as you grow.</p>
<h2>Cost #1: Lock-in limits your options<a class="anchor-link" id="cost-1-lock-in-limits-your-options"></a></h2>
<p>Lock-in is a financial and strategic cost. Proprietary databases encourage deep reliance on vendor-specific extensions, APIs, drivers, and management tooling that make migrations complex and risky.</p>
<p>Over time, these dependencies become embedded in application logic, operational runbooks, and compliance documentation, and raise the exit cost every year.&#8203;</p>
<p>This lock-in becomes visible precisely when you need maximum flexibility:</p>
<ul>
<li>Cloud strategy shifts (moving from single-cloud to multi-cloud or hybrid) expose how hard it is to re-platform proprietary workloads. Many financial institutions are pursuing multi-cloud or hybrid strategies to meet regulatory requirements (<a href="https://www.eiopa.europa.eu/digital-operational-resilience-act-dora_en">DORA in Europe</a>).</li>
<li>Mergers and acquisitions create duplicate database estates that cannot be consolidated easily because of incompatible vendors and licensing models.</li>
<li>Cost-cutting mandates force leadership to accept high database spend because the transition cost is perceived as even higher.</li>
</ul>
<p>Going open-source becomes a viable alternative. Open source databases keep leverage on your side by using standard interfaces and portable architectures. So you can adjust infrastructure, cloud providers, and operating models without rewriting your core.</p>
<p>Percona&rsquo;s <strong>multi-vendor expertise</strong> across MySQL, PostgreSQL, MongoDB, and others is designed to preserve that flexibility while still delivering enterprise-grade support.</p>
<h2>Cost #2: Performance tuning becomes a paid feature<a class="anchor-link" id="cost-2-performance-tuning-becomes-a-paid-feature"></a></h2>
<p>Many proprietary platforms offer advanced observability, query analytics, and tuning tools that are available through separate licenses, packs, or cloud add-ons. And you have to pay extra just to monitor what your database is doing in production, right when performance issues, slowdowns, or outages are already affecting your customers. The paradox is that you end up spending more only after the system has already proven to be inadequate.</p>
<p>That model creates a reactive financial posture. Instead of having 24/7 transparency, organizations find themselves &ldquo;unlocking&rdquo; features only after a crisis has already occurred. You end up rewarding the vendor with more revenue because their system proved inadequate for your current workload.</p>
<p>On the other hand, open source tooling such as <a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management">Percona Monitoring and Management (PMM)</a> provides deep query-level visibility, historical performance data, and health checks as part of an open, community-accessible stack.</p>
<p>You are not charged more for insight, you gain observability by default and can invest in expertise rather than feature unlocks.</p>
<h2>Cost #3: Scaling means paying twice<a class="anchor-link" id="cost-3-scaling-means-paying-twice"></a></h2>
<p>When you scale a proprietary database, you pay twice, once for the underlying infrastructure and again for the licenses tied to that infrastructure. Processor-based licensing or per-core licensing means that adding capacity for peak trading days, new AI scoring models, or additional test environments increases software costs.</p>
<p>This occurs even if revenue per transaction does not grow at the same rate. More cores and nodes do not necessarily represent proportional business value, but they do represent proportional license spend.&#8203;</p>
<p>In cloud DBaaS models, markups and tier structure amplify this effect. Typical high-availability PostgreSQL workloads can <a href="https://www.percona.com/resources/save-on-public-dbaas-costs">cost more than 100% more on proprietary DBaaS</a> than on open-source deployments on Kubernetes or self-managed infrastructure.</p>
<p>Efficient architectures, read replicas, multi-region setups, and extra capacity for resilience are effectively punished by higher recurring bills rather than rewarded for robustness.</p>
<p>With Percona&rsquo;s open source approach, organizations scale for performance and resilience without paying the double tax of proprietary licensing and cloud markups.</p>
<h2>Cost #4: Support that&rsquo;s aligned to the vendor, not you<a class="anchor-link" id="cost-4-support-thats-aligned-to-the-vendor-not-you"></a></h2>
<p>Vendor support is usually positioned as an insurance policy, but its incentives are aligned with renewals rather than with the fastest or most cost-effective solution for your team.</p>
<p>Support SLAs tend to focus on product availability and incident response boundaries rather than on end-to-end business outcomes or architectural improvements. When the &ldquo;right&rdquo; answer is to reduce dependence on premium features, simplify architecture, or migrate off the platform entirely, vendor support has little incentive to champion that path.&#8203;</p>
<p>But in contrast, support that is database-agnostic and open source&ndash;oriented can recommend changes across engines and environments, even when that reduces your spend with any given vendor.</p>
<p><a href="https://www.percona.com/services/managed-services">Percona&rsquo;s support model</a> is built on cross-engine expertise and outcome-focused services such as performance tuning, HA design, and root-cause analysis, rather than upselling proprietary options or new editions. The goal is to optimize your environment, not your license stack.</p>
<h2>Cost #5: Operational inflexibility<a class="anchor-link" id="cost-5-operational-inflexibility"></a></h2>
<p>Proprietary ecosystems often include tightly coupled tooling such as backup utilities, monitoring dashboards, deployment frameworks, and security controls that work with that vendor&rsquo;s stack.</p>
<p>And over time, this creates tool sprawl and operational silos. It necessitates separate processes for different proprietary databases, such as one for Oracle and another for SQL Server, and distinct runbooks for each cloud-specific DBaaS.</p>
<p>Teams spend more time working around tool boundaries than improving reliability, which prevents standardized operations across environments. Change management, incident response, and compliance reporting have to accommodate multiple incompatible systems, increasing training overhead and the risk of errors.</p>
<p>With open source and vendor-neutral tooling such as PMM and <a href="https://www.percona.com/kuberentes-database-report">Kubernetes-based operators</a>, organizations can standardize how they deploy, monitor, and secure databases while preserving the freedom to choose engines and clouds. Operations become unified without forcing a single proprietary vendor everywhere.</p>
<h2>The hidden risk: Strategic decisions get harder over time<a class="anchor-link" id="the-hidden-risk-strategic-decisions-get-harder-over-time"></a></h2>
<p>The considerable cost of proprietary databases is that they can leave companies stuck. The longer they remain on proprietary systems, the harder it becomes to make strategic choices.</p>
<ul>
<li>Budget uncertainty: Sudden changes in licensing terms, such as shifting from perpetual to subscription or changing virtualization rules, can blow a hole in a digital transformation budget overnight.</li>
<li>Innovation blockers: If every new microservice triggers a new enterprise database license discussion, your developers will stop proposing new services. Innovation is stifled by procurement friction.</li>
<li>Long-term planning: When the database is a black box, you cannot accurately predict how it will behave under the next generation of AI workloads, which leads to a conservative, often slower roadmap execution.</li>
</ul>
<h2>What open source changes (when done right)<a class="anchor-link" id="what-open-source-changes-when-done-right"></a></h2>
<p>Open source changes the cost through removing artificial constraints and making pricing a function of infrastructure and expertise instead of vendor permissions.</p>
<p>Organizations pay for compute, storage, and operational skills to run databases like MySQL, PostgreSQL, or MongoDB, not per-core licenses or premium features. It ensures costs are predictable and scale with actual usage, not licensing thresholds.</p>
<p>Open source databases also maximize:</p>
<ul>
<li><strong>Freedom of movement:</strong> Move across clouds (<a href="https://www.percona.com/blog/percona-on-the-aws-database-blog/">AWS</a>, <a href="https://www.percona.com/blog/google-cloud-platform-mysql-at-scale-with-reliable-ha-webinar-qa/">GCP</a>, <a href="https://www.percona.com/blog/embrace-the-cloud-with-microsoft-azure/">Azure</a>), environments (VMs, Containers), and distributions without asking for permission or renegotiating a contract.</li>
<li><strong>Full visibility:</strong> Open source gives your <a href="https://www.percona.com/blog/the-most-important-skills-for-an-sre-dbre-or-dba/">SREs</a> and architects the ability to &ldquo;look under the hood,&rdquo; leading to faster root-cause analysis and more resilient systems.</li>
</ul>
<p>But open source alone is not enough. Execution, architecture design, performance tuning, HA/DR planning, and day-two operations determine whether open source remains a cost advantage or becomes a different kind of complexity.</p>
<h2>Where Percona fits in<a class="anchor-link" id="where-percona-fits-in"></a></h2>
<p>Percona bridges the gap between the freedom of open source and the security requirements of modern finance. Its distributions for <a href="https://www.percona.com/mysql/support-and-services">MySQL</a>, <a href="https://www.percona.com/postgresql/software">PostgreSQL</a>, and <a href="https://www.percona.com/mongodb/software">MongoDB</a> upgrade community editions for production with security, HA integrations, backup/restore, and performance tuning built in. Companies gain top-level capabilities without layered licensing fees or edition gates.&#8203;</p>
<p>Percona also provides:</p>
<ul>
<li><a href="https://www.percona.com/services/support">24&times;7 support</a> and consulting across multiple engines and environments (on-prem, cloud, Kubernetes). Focused on minimizing downtime and optimizing TCO rather than expanding proprietary footprint.</li>
<li><a href="https://percona.community/blog/2023/03/06/monitor-your-databases-with-open-source-tools-like-pmm/">Open observability through PMM</a> to give teams a single place to monitor and manage all supported databases with full transparency and no per-node monitoring tax.&#8203;</li>
</ul>
<p>Simply put, Percona reduces long-term database spend by aligning services with customer outcomes, availability, performance, and cost control.</p>
<h2>Conclusion: Pay for control, not constraints<a class="anchor-link" id="conclusion-pay-for-control-not-constraints"></a></h2>
<p>Proprietary databases promise certainty but can create dependence. They offer predictable costs in the short term but often lead to long-term limitations and increasing markups. Open source databases, when used and managed properly, shift the focus from restrictions to options by letting you use your budget to buy infrastructure, skills, and flexibility instead of just permission to grow.</p>
<p><strong>The important question is not <em>&ldquo;How much does this license cost this year?&rdquo;</em> but rather <em>&ldquo;What will it cost us to remain flexible over the next five years?&rdquo;</em></strong></p>
<p>With open source <a href="https://www.percona.com/solutions/database-observability-monitoring-and-management">databases supported by Percona</a>, financial institutions can answer that question with confidence since they are paying for control and options, not restrictions.</p>
<p>The post <a href="https://www.percona.com/blog/what-youre-really-paying-for-with-proprietary-databases/">What You&rsquo;re Really Paying for With Proprietary Databases</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/what-youre-really-paying-for-with-proprietary-databases/">What You’re Really Paying for With Proprietary Databases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Hidden Gem: Create Aggregate Function</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-hidden-gem-create-aggregate-function/" />
      <id>https://mariadb.org/mariadb-hidden-gem-create-aggregate-function/</id>
      <updated>2026-06-04T10:35:49+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>Have you ever written a query where the GROUP BY was easy, but the aggregate was the problem?<br />
You know how to group the rows.You know what result you want for each group.But none of the built-in aggregate functions really match your logic. …<br />
Continue reading \"MariaDB Hidden Gem: Create Aggregate Function\"<br />
The post MariaDB Hidden Gem: Create Aggregate Function appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-hidden-gem-create-aggregate-function/">MariaDB Hidden Gem: Create Aggregate Function</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Have you ever written a query where the GROUP BY was easy, but the aggregate was the problem?<br>
You know how to group the rows.You know what result you want for each group.But none of <a href="https://mariadb.com/docs/server/reference/sql-functions/aggregate-functions">the built-in aggregate functions</a> really match your logic. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-hidden-gem-create-aggregate-function/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Hidden Gem: Create Aggregate Function&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-hidden-gem-create-aggregate-function/">MariaDB Hidden Gem: Create Aggregate Function</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-hidden-gem-create-aggregate-function/">MariaDB Hidden Gem: Create Aggregate Function</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Celebrating the MariaDB Foundation Sea Lions Champions Nominees</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/celebrating-the-mariadb-foundation-sea-lions-champions-nominees/" />
      <id>https://mariadb.org/celebrating-the-mariadb-foundation-sea-lions-champions-nominees/</id>
      <updated>2026-06-03T08:47:26+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>The MariaDB Foundation Sea Lions Champions recognize individuals and organizations that strengthen the MariaDB ecosystem through technical excellence, community leadership, open-source stewardship, ecosystem impact, and real-world adoption. …<br />
Continue reading \"Celebrating the MariaDB Foundation Sea Lions Champions Nominees\"<br />
The post Celebrating the MariaDB Foundation Sea Lions Champions Nominees appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/celebrating-the-mariadb-foundation-sea-lions-champions-nominees/">Celebrating the MariaDB Foundation Sea Lions Champions Nominees</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>The MariaDB Foundation Sea Lions Champions recognize individuals and organizations that strengthen the MariaDB ecosystem through technical excellence, community leadership, open-source stewardship, ecosystem impact, and real-world adoption. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/celebrating-the-mariadb-foundation-sea-lions-champions-nominees/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Celebrating the MariaDB Foundation Sea Lions Champions Nominees&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/celebrating-the-mariadb-foundation-sea-lions-champions-nominees/">Celebrating the MariaDB Foundation Sea Lions Champions Nominees</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/celebrating-the-mariadb-foundation-sea-lions-champions-nominees/">Celebrating the MariaDB Foundation Sea Lions Champions Nominees</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Foundation: Bringing TPC-B Back To Life</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-foundation-bringing-tpc-b-back-to-life/" />
      <id>https://mariadb.org/mariadb-foundation-bringing-tpc-b-back-to-life/</id>
      <updated>2026-06-03T01:10:45+00:00</updated>
      <author><name>Jonathan Miller</name></author>
      <summary type="html"><![CDATA[<p>When I joined Pervasive PSQL, one of the first performance test cases I was introduced to was TPC-B. It was already implemented inside Pervasive PSQL and it quickly became one of the most important tools in my daily work. …<br />
Continue reading \"MariaDB Foundation: Bringing TPC-B Back To Life\"<br />
The post MariaDB Foundation: Bringing TPC-B Back To Life appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-bringing-tpc-b-back-to-life/">MariaDB Foundation: Bringing TPC-B Back To Life</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>When I joined <a href="https://en.wikipedia.org/wiki/Pervasive_Software">Pervasive PSQL</a>, one of the first performance test cases I was introduced to was <a href="https://www.tpc.org/tpcb/default5.asp">TPC-B</a>. It was already implemented inside <a href="https://en.wikipedia.org/wiki/Pervasive_Software">Pervasive PSQL </a>and it quickly became one of the most important tools in my daily work. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-foundation-bringing-tpc-b-back-to-life/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Foundation: Bringing TPC-B Back To Life&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-bringing-tpc-b-back-to-life/">MariaDB Foundation: Bringing TPC-B Back To Life</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-bringing-tpc-b-back-to-life/">MariaDB Foundation: Bringing TPC-B Back To Life</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Community Server Corrective Releases</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-community-server-corrective-releases/" />
      <id>https://mariadb.org/mariadb-community-server-corrective-releases/</id>
      <updated>2026-06-02T19:20:28+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>MariaDB Community Server corrective releases are now available for the currently maintained long-term series. These releases address critical CVEs, and we strongly recommend that all users review the security advisories and upgrade as soon as possible. …<br />
Continue reading \"MariaDB Community Server Corrective Releases\"<br />
The post MariaDB Community Server Corrective Releases appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-community-server-corrective-releases/">MariaDB Community Server Corrective Releases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB Community Server corrective releases are now available for the currently maintained long-term series. These releases address critical CVEs, and we strongly recommend that all users review the security advisories and upgrade as soon as possible. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-community-server-corrective-releases/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Community Server Corrective Releases&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-community-server-corrective-releases/">MariaDB Community Server Corrective Releases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-community-server-corrective-releases/">MariaDB Community Server Corrective Releases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Enterprise Server Q1 2026 Corrective Releases</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/mariadb-enterprise-server-q1-2026-corrective-releases/" />
      <id>https://mariadb.com/resources/blog/mariadb-enterprise-server-q1-2026-corrective-releases/</id>
      <updated>2026-06-02T18:38:08+00:00</updated>
      <author><name>Daniel Bartholomew</name></author>
      <summary type="html"><![CDATA[<p>New corrective maintenance releases for MariaDB Enterprise Server 11.8.6-4, 11.4.10-8, and 10.6.25-22 are now available. Download Now Notable Release Updates […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-enterprise-server-q1-2026-corrective-releases/">MariaDB Enterprise Server Q1 2026 Corrective Releases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>New corrective maintenance releases for MariaDB Enterprise Server 11.8.6-4, 11.4.10-8, and 10.6.25-22 are now available. Download Now MariaDB Enterprise Server is an enhanced, hardened and secured version of MariaDB Community Server that delivers enterprise reliability, stability and long-term support as well as greater operational efficiency when it comes to&hellip;</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-enterprise-server-q1-2026-corrective-releases/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-enterprise-server-q1-2026-corrective-releases/">MariaDB Enterprise Server Q1 2026 Corrective Releases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>The Percona Community Slack is open — come hang out</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/06/02/percona-community-slack-open/" />
      <id>https://percona.community/blog/2026/06/02/percona-community-slack-open/</id>
      <updated>2026-06-02T11:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>The Percona Community Slack is open — come hang out There’s a new place for the people behind the databases to actually talk to each other.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/06/02/percona-community-slack-open/">The Percona Community Slack is open — come hang out</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<h1 id="the-percona-community-slack-is-open--come-hang-out">The Percona Community Slack is open &mdash; come hang out<a class="anchor-link" id="the-percona-community-slack-is-open-come-hang-out"></a></h1>
<p>There&rsquo;s a new place for the people behind the databases to actually talk to each other.</p>
<p>The Percona Community Slack is open. Right now it&rsquo;s one channel &mdash; General &mdash; and that&rsquo;s intentional. It&rsquo;s a place for DBAs, developers, contributors, and database people of all kinds to meet, swap stories, and get to know who else is out there running open source databases for a living. No silos. No sub-channels for every topic. Just a room.</p>
<h2 id="what-its-for">What it&rsquo;s for<a class="anchor-link" id="what-its-for"></a></h2>
<p>Come here to talk shop. Share what you&rsquo;re building, breaking, or fixing. Post about the migration that went sideways, the config that finally clicked, the pager incident you survived. Ask the kind of questions that belong in a conversation rather than a ticket &mdash; &ldquo;how do other people handle X?&rdquo; is exactly the right energy.</p>
<p>It&rsquo;s also where we&rsquo;ll share events we&rsquo;re attending and, when we have tickets or a spare seat, offer them to the community first. If Percona is heading to a conference near you, this is where you&rsquo;ll hear about it. And if you&rsquo;re going somewhere yourself &mdash; a meetup, a conference, a local user group &mdash; tell us. There might be community members nearby who want to meet up.</p>
<p>That&rsquo;s the point, really. Less broadcast, more conversation.</p>
<h2 id="what-its-not-for">What it&rsquo;s not for<a class="anchor-link" id="what-its-not-for"></a></h2>
<p>Technical support questions belong on the <a href="https://forums.percona.com" target="_blank" rel="noopener noreferrer">Percona Community Forums</a>. Forum answers are searchable and don&rsquo;t disappear into scrollback. Percona engineers and experienced community members watch the forums for questions. Your problem is more likely to get a useful answer there &mdash; and it&rsquo;ll help the person who hits the same issue three months from now.</p>
<p>If you post a support question in Slack, expect to be pointed to the forums. That&rsquo;s not a brush-off.</p>
<h2 id="a-few-things-that-make-this-work">A few things that make this work<a class="anchor-link" id="a-few-things-that-make-this-work"></a></h2>
<p><strong>Introduce yourself.</strong> One or two sentences about what you work on and where in the world you are. That&rsquo;s it. You don&rsquo;t need a bio.</p>
<p><strong>Share what you&rsquo;re up to.</strong> An event you&rsquo;re going to, a tool you&rsquo;ve been testing, a war story from production. The low-key post about a thing you just dealt with is exactly what people come here for.</p>
<p><strong>Lurk freely.</strong> You don&rsquo;t have to post to belong. Read, learn, jump in when you have something to say.</p>
<h2 id="the-short-version-of-the-rules">The short version of the rules<a class="anchor-link" id="the-short-version-of-the-rules"></a></h2>
<p>Be the person you&rsquo;d want to share an on-call rotation with.</p>
<p>Treat everyone as a peer. Assume good faith. No harassment. Critique technology on technical merits. Don&rsquo;t cold-DM people with pitches. Keep private things private. If something needs a moderator&rsquo;s attention, DM one directly &mdash; reports stay confidential.</p>
<h2 id="come-in">Come in<a class="anchor-link" id="come-in"></a></h2>
<p>If you&rsquo;re a DBA, a developer, a contributor, or just someone who runs databases and occasionally wants to talk to other people who run databases &mdash; you belong here.</p>
<p><a href="http://percona.slack.com/" target="_blank" rel="noopener noreferrer">Join the Percona Community Slack &rarr;</a></p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/06/02/percona-community-slack-open/">The Percona Community Slack is open — come hang out</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>A New Pull Request Processing Time Record</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/a-new-pull-request-processing-time-record/" />
      <id>https://mariadb.org/a-new-pull-request-processing-time-record/</id>
      <updated>2026-06-01T14:25:33+00:00</updated>
      <author><name>Georgi Kodinov</name></author>
      <summary type="html"><![CDATA[<p>We have a new record average time to process a pull request: 21 days!<br />
Part of my job is following (and trying to improve of course) some key metrics about MariaDB Server pull request processing. …<br />
Continue reading \"A New Pull Request Processing Time Record\"<br />
The post A New Pull Request Processing Time Record appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/a-new-pull-request-processing-time-record/">A New Pull Request Processing Time Record</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>We have a new record average time to process a pull request: 21 days!<br>
Part of my job is following (and trying to improve of course) some key metrics about MariaDB Server pull request processing. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/a-new-pull-request-processing-time-record/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;A New Pull Request Processing Time Record&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/a-new-pull-request-processing-time-record/">A New Pull Request Processing Time Record</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/a-new-pull-request-processing-time-record/">A New Pull Request Processing Time Record</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Building Smart Semantic Search using PostgreSQL and pgvector. Case Study &#8211; Part 2 &#8211; Postgres Layer</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/05/31/semantic-search-on-postgresql-part-2/" />
      <id>https://percona.community/blog/2026/05/31/semantic-search-on-postgresql-part-2/</id>
      <updated>2026-05-31T11:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>I’ll explain how I built the Postgres layer for semantic vector search on the Percona Community website: pgvector, chunks, two table modifications, the database schema, how the indexer populates Postgres, and what the SELECT statement looks like during a search.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/31/semantic-search-on-postgresql-part-2/">Building Smart Semantic Search using PostgreSQL and pgvector. Case Study &#8211; Part 2 &#8211; Postgres Layer</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>I&rsquo;ll explain how I built the <strong>Postgres layer</strong> for semantic vector search on the Percona Community website: pgvector, chunks, two table modifications, the database schema, how the indexer populates Postgres, and <strong>what the SELECT statement looks like during a search</strong>.</p>
<blockquote>
<p><a href="https://percona.community/blog/2026/05/29/semantic-search-on-postgresql-part-1/">Part 1</a>: why semantic search, what&rsquo;s already working on the site, the widget, and an overview of the stack.</p>
</blockquote>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-2-postgres-website-search.png" alt="Percona.community website search"></figure>
</p>
<h2 id="architecture">Architecture<a class="anchor-link" id="architecture"></a></h2>
<p>Search runs separately from the website at <strong>search.percona.community</strong>: FastAPI, a background indexer, and PostgreSQL with pgvector are all in a single Docker Compose file. <a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community</a> remains static on Hugo and GitHub Pages, it doesn&rsquo;t write directly to the database.</p>
<pre class="mermaid">
flowchart TB
subgraph users["Users"]
direction TB
User(["User"]) --&gt; Widget["Widget &middot; percona.community"]
Admin(["Admin"]) --&gt; Dash["Admin dashboard &middot; /demo"]
Site["Site &middot; RSS + HTML"]
end
subgraph app["Application"]
direction TB
API["FastAPI &middot; search.percona.community"]
Model["nomic-embed-text-v1"]
Worker["Indexer worker"]
API --&gt;|embed query| Model
Model --&gt;|embedding| API
Worker --&gt;|embed chunks| Model
Model --&gt;|embeddings| Worker
end
subgraph data["Database"]
direction TB
DB[("PostgreSQL + pgvector")]
end
Widget --&gt; API
Dash --&gt; API
Site --&gt; Worker
API --&gt;|read vectors &middot; write queue/history| DB
Worker --&gt;|write vectors &middot; read queue| DB
</pre>
<h3 id="search">Search<a class="anchor-link" id="search"></a></h3>
<p>A visitor enters a query into the widget. The widget sends a <code>POST /search</code> request to FastAPI. The service computes the query embedding with nomic with the prefix <code>search_query:</code> and searches for the nearest vectors in Postgres. The widget knows nothing about pgvector, it only receives JSON with links.</p>
<h3 id="admin-dashboard">Admin dashboard<a class="anchor-link" id="admin-dashboard"></a></h3>
<p>On the same FastAPI service I run an admin dashboard at <code>/demo</code>: test queries, search history, a database summary, viewing documents and chunks. The dashboard does not talk to Postgres directly, it only calls the API; the API reads and writes Postgres (<code>search_history</code>, <code>index_queue</code>, search results).</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-status.png" alt="Admin dashboard - Dashboard"></figure>
</p>
<h3 id="indexing">Indexing<a class="anchor-link" id="indexing"></a></h3>
<p>To refresh the index, I click <strong>Start Indexing</strong> in the dashboard, that hits <code>POST /index/start</code>. The same endpoint can be called from outside: a GitHub webhook after a push to the site repo, cron, or curl while debugging. FastAPI enqueues the job in <code>index_queue</code>. A worker in the indexer container picks it up, downloads RSS and HTML from the site, splits text into chunks, computes vectors with nomic (<code>search_document:</code>), and writes to <code>pages</code>, <code>community_nomic</code>, and <code>indexer_runs</code>. The crawl runs in the background and does not block HTTP.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index.png" alt="Admin dashboard - Indexing"></figure>
</p>
<p>Important limitation: the indexer and the API must use the same embedding model. The query vector and the vectors in the database must be from the same space, otherwise, cosine similarity doesn&rsquo;t make sense.</p>
<h2 id="pgvector-in-postgres">pgvector in Postgres<a class="anchor-link" id="pgvector-in-postgres"></a></h2>
<p>For semantic search, you don&rsquo;t need an LLM, but an <strong>embedding model</strong>: a string as input, a vector as output. I chose <strong>nomic-embed-text-v1</strong>, 768-dimensional, running via <code>sentence-transformers</code> on the CPU, without a paid API.</p>
<p>I&rsquo;m using <strong><a href="https://docs.percona.com/postgresql/18/index.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL 18</a></strong>, pgvector is already included in the distribution; <code>CREATE EXTENSION vector</code>, and you&rsquo;re done (<a href="https://docs.percona.com/postgresql/18/enable-extensions.html#pgvector" target="_blank" rel="noopener noreferrer">documentation</a>).</p>
<p>The basic structure is a column with <strong>fixed dimensions</strong> for the model:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">sql</span><button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-1">
<div class="highlight">
<pre class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="n">EXTENSION</span><span class="w"> </span><span class="k">IF</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">EXISTS</span><span class="w"> </span><span class="n">vector</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">chunks</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="nb">SERIAL</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">chunk_text</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">embedding</span><span class="w"> </span><span class="n">vector</span><span class="p">(</span><span class="mi">768</span><span class="p">)</span><span class="w"> </span><span class="c1">-- exactly 768 under nomic
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">);</span></span></span></code></pre>
</div>
</div>
</div>
<p>In the project, the table is named <code>community_nomic</code>: the prefix <code>community_</code> (site) + the model key <code>nomic</code>. I&rsquo;m setting up a comparison of embedding models: <strong>each</strong> model has <strong>its own</strong> vector table (<code>community_</code>), because the dimensions and embedding spaces are different, so they can&rsquo;t be mixed in a single table. Currently, there is one model in the project, <strong>nomic-embed-text-v1</strong>, 768 dimensions; later, I can add a second table <code>community_</code> and switch the index/API via <code>EMBEDDING_MODEL_KEY</code>.</p>
<p>pgvector compares vectors with several <strong>distance operators</strong>. I search with <strong>cosine distance</strong> (the <code></code> operator in SQL): the smaller the distance, the closer the match. In the widget and API I show <strong>similarity</strong>, not the raw distance, <code>similarity = 1 - distance</code>, so a higher score means a better hit. The operators:</p>
<table>
<thead>
<tr>
<th>Operator</th>
<th>When useful</th>
</tr>
</thead>
<tbody>
<tr>
<td><code></code></td>
<td>L2 (Euclidean)</td>
</tr>
<tr>
<td><code></code></td>
<td>inner product</td>
</tr>
<tr>
<td><code></code></td>
<td><strong>cosine</strong>, my choice for nomic</td>
</tr>
</tbody>
</table>
<p>Simplified search for &ldquo;nearest chunks&rdquo;:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">sql</span><button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-2">
<div class="highlight">
<pre class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">slug</span><span class="p">,</span><span class="w"> </span><span class="n">chunk_text</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="p">(</span><span class="n">embedding</span><span class="w"> </span><span class="o"></span><span class="w"> </span><span class="err">$</span><span class="n">query_vector</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">score</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">community_nomic</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">embedding</span><span class="w"> </span><span class="o"></span><span class="w"> </span><span class="err">$</span><span class="n">query_vector</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">LIMIT</span><span class="w"> </span><span class="mi">20</span><span class="p">;</span></span></span></code></pre>
</div>
</div>
</div>
<p>The threshold in the API is <code>min_score</code> (my default is <strong>0.52</strong>): anything lower is discarded. On beta I tuned this number for a while, the results changed noticeably depending on this single parameter.</p>
<p>To avoid scanning the entire table as the index grows, I set up an <strong>HNSW</strong> index (approximate nearest neighbor search):</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">sql</span><button class="code-block__copy" type="button" data-copy-target="codeblock-3" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-3">
<div class="highlight">
<pre class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">community_nomic</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">USING</span><span class="w"> </span><span class="n">hnsw</span><span class="w"> </span><span class="p">(</span><span class="n">embedding</span><span class="w"> </span><span class="n">vector_cosine_ops</span><span class="p">);</span></span></span></code></pre>
</div>
</div>
</div>
<p>At this scale, a separate vector database wasn&rsquo;t necessary, a single Postgres instance handles metadata, vectors, and search.</p>
<h2 id="postgres-in-docker-docker-compose">Postgres in Docker: <code>docker-compose</code><a class="anchor-link" id="postgres-in-docker-docker-compose"></a></h2>
<p>I set up the stack using <strong>Docker Compose</strong>, Postgres, the API, and the indexer are all in containers, with the same setup locally and in production. Production, <strong>EC2 on AWS</strong> (<code>search.percona.community</code>), an ARM instance, using the same <code>docker-compose</code>.</p>
<p>In <code>docker-compose.yml</code>, Postgres on Percona looks like this (on Mac ARM):</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">yaml</span><button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-4">
<div class="highlight">
<pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">postgres</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">percona/percona-distribution-postgresql:18.1-3-arm64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">POSTGRES_USER</span><span class="p">:</span><span class="w"> </span><span class="l">postgres</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">POSTGRES_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">postgres</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">POSTGRES_DB</span><span class="p">:</span><span class="w"> </span><span class="l">community_search</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="s2">"5433:5432"</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">pgdata:/var/lib/postgresql/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">./init:/docker-entrypoint-initdb.d</span></span></span></code></pre>
</div>
</div>
</div>
<p>In <code>init/01-enable-pgvector.sql</code>, include only <code>CREATE EXTENSION IF NOT EXISTS vector</code>. If you&rsquo;re developing on <strong>x86</strong>, <strong>use</strong> amd64 in the image tag instead of <code>arm64</code>, see the options in the <a href="https://docs.percona.com/postgresql/18/index.html" target="_blank" rel="noopener noreferrer">Percona documentation</a>. I left <strong>arm64</strong> on both Mac and EC2: the configuration is the same.</p>
<p>I view the tables and data in <strong>pgAdmin</strong>. The <code>pages</code>, <code>community_nomic</code>, and service tables themselves are created when the API and indexer start using <code>ensure_*</code> functions in the code: these are <code>CREATE TABLE IF NOT EXISTS</code> and <code>CREATE INDEX IF NOT EXISTS</code>, not a separate migration directory.</p>
<h2 id="indexing-and-chunking">Indexing and Chunking<a class="anchor-link" id="indexing-and-chunking"></a></h2>
<p>The site doesn&rsquo;t write directly to the database, the database is populated by an <strong>indexer</strong>: a worker fetches RSS and HTML from <a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community</a>, splits the text into chunks, computes embeddings, and writes the rows to <code>community_nomic</code> and <code>pages</code>. The widget and API only read what has already been written during searches.</p>
<h3 id="why-chunks">Why Chunks<a class="anchor-link" id="why-chunks"></a></h3>
<p>At first, I tried <strong>a single vector for the entire article</strong>. I quickly ran into three problems:</p>
<ul>
<li>Long text takes longer to encode and consumes more memory;</li>
<li>The model has an input length limit;</li>
<li>a single vector for long text <strong>blurs</strong> the meaning, a query about a specific paragraph doesn&rsquo;t map well to the &ldquo;averaged&rdquo; embedding of the entire article.</li>
</ul>
<p>I settled on a <strong>400-word</strong> window with a <strong>50</strong>-word overlap (<code>chunker.py</code>). Each chunk is a separate line with its own <code>embedding</code>.</p>
<p>The first version of the chunker sliced <strong>only the body</strong> of the article, without the title, author, date, or tags. For queries like &ldquo;articles by a certain author,&rdquo; the results were off: the model saw the text but not the document&rsquo;s context. I added <strong>metadata to each chunk</strong>, a <code>Title / Author / Date / Tags / Type</code> block at the beginning of each fragment before calculating the vector.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-chunking.png" alt="Admin dashboard - Chunking"></figure>
</p>
<p>When searching, the API finds the closest chunks, but the card shows <strong>the best chunk for the document</strong> (one <code>slug</code>, one card). Without this, a long article would clutter the results with multiple lines.</p>
<h2 id="database-schema-two-revisions-of-the-chunk-tables">Database Schema: Two Revisions of the Chunk Tables<a class="anchor-link" id="database-schema-two-revisions-of-the-chunk-tables"></a></h2>
<p>I revised the chunk storage schema <strong>twice</strong>, and separately added utility tables for background indexing and search logs.</p>
<h3 id="version-1-everything-in-a-single-table">Version 1: Everything in a Single Table<a class="anchor-link" id="version-1-everything-in-a-single-table"></a></h3>
<p>The first working schema was <strong>a single table for all chunks and document information</strong>: each row represented a single article fragment, and it also contained duplicated page metadata (<strong>url, title, author, date, tags, content_type</strong>) along with <code>chunk_text</code> and <code>embedding</code>.</p>
<p>Pros: one <code>INSERT</code>, one <code>SELECT</code>, no joins.</p>
<p>Cons I encountered:</p>
<ul>
<li>one article, dozens of identical copies of title and author;</li>
<li>when updating a page, it&rsquo;s easy to get out of sync (one title in chunk #0, another in chunk #3);</li>
<li>fetching the image and description for the card from <code>chunk_text</code> was unreliable.</li>
</ul>
<p>Conclusion: A <strong>vector layer</strong> and a <strong>card in the UI</strong> serve different purposes.</p>
<p>The code still includes <code>_migrate_chunks_table</code>: when the API and indexer start up (inside <code>ensure_content_table</code>), it drops any extra columns from the chunk table if they are left over from the old prototype.</p>
<h3 id="version-2-pages--community_nomic">Version 2: <code>pages</code> + <code>community_nomic</code><a class="anchor-link" id="version-2-pages-community_nomic"></a></h3>
<p>I split the data into two tables:</p>
<ul>
<li><strong><code>pages</code></strong>, one row per document: url, title, type, author, date, tags, images, description.</li>
<li><strong><code>community_nomic</code></strong>, only chunks: slug, chunk_index, chunk_text, embedding.</li>
</ul>
<p>They are linked by <code>slug</code> (stable key from the URL). Search: find the nearest chunks in <code>community_nomic</code>, assemble the card from <code>pages</code>.</p>
<p>In the admin dashboard I can open any indexed document and see what landed in <code>pages</code> (metadata, image, description) and what text was split into chunks.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-details.png" alt="Admin dashboard, document details (pages) and chunks"></figure>
</p>
<p>HNSW on <code>community_nomic</code>:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">sql</span><button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-5">
<div class="highlight">
<pre class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="p">...</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">community_nomic</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">USING</span><span class="w"> </span><span class="n">hnsw</span><span class="w"> </span><span class="p">(</span><span class="n">embedding</span><span class="w"> </span><span class="n">vector_cosine_ops</span><span class="p">);</span></span></span></code></pre>
</div>
</div>
</div>
<p>That&rsquo;s why, when switching models, I don&rsquo;t reuse <code>community_nomic</code>; instead, I create a new table and re-index it. A single search query involves vectors from <strong>only one</strong> model, both during indexing and in the API.</p>
<h2 id="indexer-rss-http-and-queue">Indexer: RSS, HTTP, and Queue<a class="anchor-link" id="indexer-rss-http-and-queue"></a></h2>
<p>The indexer is a separate container that crawls <a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community</a> and populates the database. It starts with <strong>RSS</strong>, four feeds:</p>
<ul>
<li><a href="https://percona.community/blog/index.xml" target="_blank" rel="noopener noreferrer">blog/index.xml</a></li>
<li><a href="https://percona.community/events/index.xml" target="_blank" rel="noopener noreferrer">events/index.xml</a></li>
<li><a href="https://percona.community/talks/index.xml" target="_blank" rel="noopener noreferrer">talks/index.xml</a></li>
<li><a href="https://percona.community/contributors/index.xml" target="_blank" rel="noopener noreferrer">contributors/index.xml</a></li>
</ul>
<p>RSS feeds contain a title, link, date, author, tags, and often a short description, but <strong>not the full text of the article</strong>. For each entry, I perform an <strong>HTTP GET</strong> on the HTML page and extract the main content (in <code>crawler.py</code>). If the HTML is empty, I fall back to the description from the RSS feed.</p>
<p>The <strong>Index</strong> and <strong>Status</strong> tabs in the dashboard, without them, debugging the crawl and embedding would have been a guessing game.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-running.png" alt="Admin dashboard - Index Running"></figure>
</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-history.png" alt="Admin dashboard - Index history"></figure>
</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-2-postgres-dashboard-index-stats.png" alt="Admin dashboard - Index overview"></figure>
</p>
<h2 id="table-schema">Table Schema<a class="anchor-link" id="table-schema"></a></h2>
<p>All tables are created when the API and indexer start (<code>ensure_*</code> in code, <code>CREATE TABLE IF NOT EXISTS</code>, <code>CREATE INDEX IF NOT EXISTS</code>). There is no separate migrations folder. I don&rsquo;t use foreign keys between search and utility tables: reindexing deletes and re-inserts rows by <code>slug</code>, and the queue tables are only loosely linked.</p>
<h3 id="search-data">Search data<a class="anchor-link" id="search-data"></a></h3>
<p><strong><code>pages</code></strong> and <strong><code>community_nomic</code></strong> are linked by <code>slug</code> (no FK). The indexer writes both; the API reads them on <code>POST /search</code>.</p>
<h4 id="pages">pages</h4>
<p>One row per document.</p>
<table>
<thead>
<tr>
<th>Column</th>
<th>Type</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>slug</code></td>
<td>TEXT</td>
<td>primary key, stable key from the URL</td>
</tr>
<tr>
<td><code>url</code></td>
<td>TEXT</td>
<td>canonical link (UNIQUE)</td>
</tr>
<tr>
<td><code>content_type</code></td>
<td>TEXT</td>
<td>blog, event, talk, contributor</td>
</tr>
<tr>
<td><code>title</code></td>
<td>TEXT</td>
<td>card title</td>
</tr>
<tr>
<td><code>date</code></td>
<td>TEXT</td>
<td>publication date from RSS/HTML</td>
</tr>
<tr>
<td><code>author</code></td>
<td>TEXT</td>
<td>author name</td>
</tr>
<tr>
<td><code>tags</code></td>
<td>TEXT[]</td>
<td>tags for search and chunk metadata</td>
</tr>
<tr>
<td><code>image_url</code></td>
<td>TEXT</td>
<td>full image from the site</td>
</tr>
<tr>
<td><code>image_thumb_url</code></td>
<td>TEXT</td>
<td>smaller image for the widget popup</td>
</tr>
<tr>
<td><code>description</code></td>
<td>TEXT</td>
<td>short description for the card</td>
</tr>
<tr>
<td><code>updated_at</code></td>
<td>TIMESTAMPTZ</td>
<td>last time the row was indexed</td>
</tr>
</tbody>
</table>
<h4 id="community_nomic">community_nomic</h4>
<p>Chunks and vectors (table name = site + model key).</p>
<table>
<thead>
<tr>
<th>Column</th>
<th>Type</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>SERIAL</td>
<td>primary key</td>
</tr>
<tr>
<td><code>slug</code></td>
<td>TEXT</td>
<td>link to <code>pages</code></td>
</tr>
<tr>
<td><code>chunk_index</code></td>
<td>INT</td>
<td>chunk position in the document (UNIQUE with <code>slug</code>)</td>
</tr>
<tr>
<td><code>chunk_text</code></td>
<td>TEXT</td>
<td>text passed to the embedding model</td>
</tr>
<tr>
<td><code>embedding</code></td>
<td>vector(768)</td>
<td>nomic vector for cosine search</td>
</tr>
</tbody>
</table>
<h3 id="utility">Utility<a class="anchor-link" id="utility"></a></h3>
<p>Three small tables for indexing and debugging.</p>
<h4 id="index_queue">index_queue</h4>
<p>Pending jobs. Written by the API.</p>
<table>
<thead>
<tr>
<th>Column</th>
<th>Type</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>SERIAL</td>
<td>primary key</td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMPTZ</td>
<td>when the job was queued</td>
</tr>
<tr>
<td><code>status</code></td>
<td>TEXT</td>
<td>pending, running, done, cancelled</td>
</tr>
<tr>
<td><code>model</code></td>
<td>TEXT</td>
<td>embedding model key (<code>nomic</code>)</td>
</tr>
<tr>
<td><code>feeds</code></td>
<td>TEXT</td>
<td>RSS feed URLs (comma-separated)</td>
</tr>
<tr>
<td><code>crawl_delay</code></td>
<td>FLOAT</td>
<td>pause between HTTP requests (seconds)</td>
</tr>
<tr>
<td><code>limit_per_type</code></td>
<td>INT</td>
<td>cap per content type (partial reindex)</td>
</tr>
<tr>
<td><code>run_id</code></td>
<td>INT</td>
<td><code>indexer_runs.id</code> once the worker starts</td>
</tr>
<tr>
<td><code>cancel_requested</code></td>
<td>BOOLEAN</td>
<td>cancel flag from the dashboard</td>
</tr>
</tbody>
</table>
<h4 id="indexer_runs">indexer_runs</h4>
<p>Crawl progress. Written by the indexer worker.</p>
<table>
<thead>
<tr>
<th>Column</th>
<th>Type</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>SERIAL</td>
<td>primary key</td>
</tr>
<tr>
<td><code>started_at</code></td>
<td>TIMESTAMPTZ</td>
<td>run start</td>
</tr>
<tr>
<td><code>finished_at</code></td>
<td>TIMESTAMPTZ</td>
<td>run end</td>
</tr>
<tr>
<td><code>status</code></td>
<td>TEXT</td>
<td>running, done, error, cancelled</td>
</tr>
<tr>
<td><code>model</code></td>
<td>TEXT</td>
<td>embedding model key</td>
</tr>
<tr>
<td><code>total_docs</code></td>
<td>INT</td>
<td>documents processed</td>
</tr>
<tr>
<td><code>total_chunks</code></td>
<td>INT</td>
<td>chunks written</td>
</tr>
<tr>
<td><code>current_url</code></td>
<td>TEXT</td>
<td>page being crawled</td>
</tr>
<tr>
<td><code>current_doc_num</code></td>
<td>INT</td>
<td>document counter</td>
</tr>
<tr>
<td><code>errors</code></td>
<td>INT</td>
<td>error count</td>
</tr>
<tr>
<td><code>message</code></td>
<td>TEXT</td>
<td>status or error text</td>
</tr>
</tbody>
</table>
<h4 id="search_history">search_history</h4>
<p>Search log. Written by the API on each <code>POST /search</code>.</p>
<table>
<thead>
<tr>
<th>Column</th>
<th>Type</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>SERIAL</td>
<td>primary key</td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMPTZ</td>
<td>query time</td>
</tr>
<tr>
<td><code>query</code></td>
<td>TEXT</td>
<td>user query</td>
</tr>
<tr>
<td><code>content_type</code></td>
<td>TEXT</td>
<td>filter: all or one type</td>
</tr>
<tr>
<td><code>limit_requested</code></td>
<td>INT</td>
<td>requested result limit</td>
</tr>
<tr>
<td><code>results_count</code></td>
<td>INT</td>
<td>rows returned</td>
</tr>
<tr>
<td><code>chunks_in_index</code></td>
<td>INT</td>
<td>snapshot: chunk count at query time</td>
</tr>
<tr>
<td><code>by_type</code></td>
<td>JSONB</td>
<td>hit counts per content type</td>
</tr>
<tr>
<td><code>prepare_ms</code></td>
<td>REAL</td>
<td>API timing breakdown</td>
</tr>
<tr>
<td><code>model_load_ms</code></td>
<td>REAL</td>
<td>model load time</td>
</tr>
<tr>
<td><code>embed_ms</code></td>
<td>REAL</td>
<td>embedding time</td>
</tr>
<tr>
<td><code>db_ms</code></td>
<td>REAL</td>
<td>Postgres search time</td>
</tr>
<tr>
<td><code>format_ms</code></td>
<td>REAL</td>
<td>JSON formatting time</td>
</tr>
<tr>
<td><code>total_ms</code></td>
<td>REAL</td>
<td>end-to-end time</td>
</tr>
<tr>
<td><code>model</code></td>
<td>TEXT</td>
<td>embedding model key</td>
</tr>
</tbody>
</table>
<h3 id="indexes">Indexes<a class="anchor-link" id="indexes"></a></h3>
<p>Created in the same <code>ensure_*</code> functions as the tables. Besides primary keys and <code>UNIQUE</code> on <code>pages.url</code> and <code>(slug, chunk_index)</code> in <code>community_nomic</code>:</p>
<ul>
<li><strong><code>pages_content_type_idx</code></strong> on <code>content_type</code>, filter by blog / event / talk / contributor in search;</li>
<li><strong><code>community_nomic_embedding_idx</code></strong>, <strong>HNSW</strong> on <code>embedding</code> (<code>vector_cosine_ops</code>); without it, nearest-neighbor search would scan the whole table as chunks grow;</li>
<li><strong><code>community_nomic_slug_idx</code></strong> on <code>slug</code>, delete all chunks for one document on reindex;</li>
<li><strong><code>search_history_created_at_idx</code></strong>, recent queries first in the dashboard History tab.</li>
</ul>
<p><code>index_queue</code> and <code>indexer_runs</code> only have a serial primary key, few rows, a full scan is fine.</p>
<h2 id="how-postgres-responds-to-a-search-query">How Postgres Responds to a Search Query<a class="anchor-link" id="how-postgres-responds-to-a-search-query"></a></h2>
<p>The API receives the query text, computes a vector with nomic (<code>search_query:</code> + text), and runs SQL that finds the nearest chunks and joins row metadata from <code>pages</code>.</p>
<h3 id="the-first-query-was-naive">The First Query Was Naive<a class="anchor-link" id="the-first-query-was-naive"></a></h3>
<p>At first, I did what the pgvector tutorials suggest, &ldquo;find the 20 closest vectors&rdquo;:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">sql</span><button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-6">
<div class="highlight">
<pre class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">slug</span><span class="p">,</span><span class="w"> </span><span class="n">chunk_index</span><span class="p">,</span><span class="w"> </span><span class="n">chunk_text</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="p">(</span><span class="n">embedding</span><span class="w"> </span><span class="o"></span><span class="w"> </span><span class="err">$</span><span class="n">query_vector</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">score</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">community_nomic</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">embedding</span><span class="w"> </span><span class="o"></span><span class="w"> </span><span class="err">$</span><span class="n">query_vector</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">LIMIT</span><span class="w"> </span><span class="mi">20</span><span class="p">;</span></span></span></code></pre>
</div>
</div>
</div>
<p>The query <strong>worked</strong>, but the results were incorrect from a UI perspective. It returns <strong>20 chunks</strong>, not <strong>20 documents</strong>. A long article with fifteen chunks could take up <strong>half the list</strong> with a single <code>slug</code>; a short post with one good paragraph didn&rsquo;t make it to the top. The user sees <strong>pages</strong> (cards with links), but we search the database by <strong>chunks</strong>, I close that gap in SQL.</p>
<h3 id="what-i-do-now">What I do now<a class="anchor-link" id="what-i-do-now"></a></h3>
<ol>
<li><strong>Join</strong> <code>community_nomic</code> + <code>pages</code> by <code>slug</code>.</li>
<li><code>ROW_NUMBER() PARTITION BY slug</code>, I keep <strong>one</strong> best chunk per document.</li>
<li><code>WHERE score &gt;= min_score</code> (default <strong>0.52</strong>).</li>
<li><code>ORDER BY score DESC LIMIT N</code>.</li>
</ol>
<p>Simplified version of the final query:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">sql</span><button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-7">
<div class="highlight">
<pre class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">WITH</span><span class="w"> </span><span class="n">ranked</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">SELECT</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">url</span><span class="p">,</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">title</span><span class="p">,</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">content_type</span><span class="p">,</span><span class="w"> </span><span class="k">c</span><span class="p">.</span><span class="n">slug</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="p">(</span><span class="k">c</span><span class="p">.</span><span class="n">embedding</span><span class="w"> </span><span class="o"></span><span class="w"> </span><span class="err">$</span><span class="n">query_vector</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">score</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">ROW_NUMBER</span><span class="p">()</span><span class="w"> </span><span class="n">OVER</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">PARTITION</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="k">c</span><span class="p">.</span><span class="n">slug</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="k">c</span><span class="p">.</span><span class="n">embedding</span><span class="w"> </span><span class="o"></span><span class="w"> </span><span class="err">$</span><span class="n">query_vector</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">rn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">community_nomic</span><span class="w"> </span><span class="k">c</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">INNER</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="n">pages</span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">slug</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">c</span><span class="p">.</span><span class="n">slug</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">-- AND p.content_type = 'blog' -- optional: filter by type
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">best_per_page</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">ranked</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">rn</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">SELECT</span><span class="w"> </span><span class="n">url</span><span class="p">,</span><span class="w"> </span><span class="n">title</span><span class="p">,</span><span class="w"> </span><span class="n">content_type</span><span class="p">,</span><span class="w"> </span><span class="n">slug</span><span class="p">,</span><span class="w"> </span><span class="n">score</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">best_per_page</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="n">score</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="mi">0</span><span class="p">.</span><span class="mi">52</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">score</span><span class="w"> </span><span class="k">DESC</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">LIMIT</span><span class="w"> </span><span class="mi">20</span><span class="p">;</span></span></span></code></pre>
</div>
</div>
</div>
<h3 id="filtering-by-content-type">Filtering by content type<a class="anchor-link" id="filtering-by-content-type"></a></h3>
<p>The site has blog, event, talk, and contributor, in the widget and on <code>/search/</code>, you can search for <strong>all at once</strong> or a single type. In the API, this is the <code>content_type</code> field in <code>POST /search</code>; in SQL, <code>AND p.content_type = %s</code> is added when a single type is selected.</p>
<h3 id="sort-order-in-the-widget">Sort order in the widget<a class="anchor-link" id="sort-order-in-the-widget"></a></h3>
<p>In SQL, results are ranked by similarity (<code>ORDER BY score DESC</code>), &ldquo;what matches the query best?&rdquo;</p>
<p>On a community site, <strong>recent material often matters as much as the top semantic match</strong>. An older article might score 0.71 while a newer post on the same topic scores 0.66. I still build the shortlist in SQL (one best chunk per document, <code>min_score</code> threshold), but the API then <strong>re-sorts blog, event, and talk by publication date</strong>, newest first. Contributors and rows without a date stay at the bottom.</p>
<p>The widget still shows the <strong>similarity score</strong> on each card so you can see why the page was included:</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-2-postgres-widget-scores.png" alt="Widget search results with similarity scores"></figure>
</p>
<h2 id="summary">Summary<a class="anchor-link" id="summary"></a></h2>
<p><strong>Postgres layer</strong>: I set this up without a separate vector DB, using pgvector in Percona, two table modifications for chunking, auxiliary tables for background indexing, HNSW, and SQL with &ldquo;best-fit chunk per document.&rdquo; The indexer processes RSS and HTML; I manage the database in pgAdmin.</p>
<p>Currently, the search index has about <strong>803</strong> documents and <strong>1,656</strong> vectors, thousands of rows, not billions. This is a community-scale setup: a single Postgres instance on EC2, embedding on the CPU, HNSW on all chunks, the solutions above were chosen with this in mind. When I add videos, GitHub issues, and the forum, the volume will grow, then I&rsquo;ll re-evaluate the indexing time and hardware.</p>
<h3 id="note-from-the-author">Note from the author<a class="anchor-link" id="note-from-the-author"></a></h3>
<p>About <strong>six months ago</strong>, I already tried to set up something similar to Postgres + vectors using AI agents. Back then, I kept running into the same issues: a clunky <strong>startup</strong> of the environment, the <strong>schema</strong> and its <strong>modifications</strong>, <strong>initializing Percona Distribution for PostgreSQL</strong>, and pgvector, the agent would either skip a step or suggest incompatible configuration snippets.</p>
<p>This time, with <a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community</a>, went better: the agent set up Compose, <code>ensure_*</code>, search SQL, and the admin dashboard, without that series of failures at startup. More time was spent on the logic (chunks, <code>min_score</code>, result ordering) rather than on &ldquo;why the database won&rsquo;t start.&rdquo;</p>
<p>If you try this setup yourself or notice any inaccuracies, please leave a comment.</p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/31/semantic-search-on-postgresql-part-2/">Building Smart Semantic Search using PostgreSQL and pgvector. Case Study &#8211; Part 2 &#8211; Postgres Layer</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Server 12.3 LTS Released</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-server-12-3-lts-released/" />
      <id>https://mariadb.org/mariadb-server-12-3-lts-released/</id>
      <updated>2026-05-29T16:47:25+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>The MariaDB Foundation is pleased to announce the availability of MariaDB Server 12.3 LTS, the latest Long Term Support release of MariaDB Server. The first GA of the 12.3 series is 12.3.2. …<br />
Continue reading \"MariaDB Server 12.3 LTS Released\"<br />
The post MariaDB Server 12.3 LTS Released appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-server-12-3-lts-released/">MariaDB Server 12.3 LTS Released</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>The MariaDB Foundation is pleased to announce the availability of MariaDB Server 12.3 LTS, the latest Long Term Support release of MariaDB Server. The first GA of the 12.3 series is 12.3.2. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-server-12-3-lts-released/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Server 12.3 LTS Released&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-server-12-3-lts-released/">MariaDB Server 12.3 LTS Released</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-server-12-3-lts-released/">MariaDB Server 12.3 LTS Released</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Building Smart Semantic Search using PostgreSQL and pgvector. Case Study &#8211; Part 1</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/05/29/semantic-search-on-postgresql-part-1/" />
      <id>https://percona.community/blog/2026/05/29/semantic-search-on-postgresql-part-1/</id>
      <updated>2026-05-29T11:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>Type “zero downtime database migration” into the site’s search bar and you’ll get articles and talks about database migration with minimal downtime, even if those words aren’t in the titles or content. This is semantic search on PostgreSQL and pgvector, without paid embedding APIs or a separate vector database. In this series I’ll cover how it works and why I chose this stack.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/29/semantic-search-on-postgresql-part-1/">Building Smart Semantic Search using PostgreSQL and pgvector. Case Study &#8211; Part 1</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Type &ldquo;zero downtime database migration&rdquo; into the site&rsquo;s search bar and you&rsquo;ll get articles and talks about database migration with minimal downtime, even if those words aren&rsquo;t in the titles or content. This is <strong>semantic search</strong> on <strong>PostgreSQL</strong> and <strong><a href="https://github.com/pgvector/pgvector" target="_blank" rel="noopener noreferrer">pgvector</a></strong>, without paid embedding APIs or a separate vector database. In this series I&rsquo;ll cover how it works and why I chose this stack.</p>
<p>I&rsquo;ll walk through how and why I built the search for our community site: blog, events, talks, and profiles. The post should help if you want to repeat the approach or need a practical case study on simple components. If you&rsquo;ve done something similar, I&rsquo;d like to hear your feedback.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-1-intro-kubernetes.png" alt="Smart Semantic Search using PostgreSQL and pgvector - Introduction"></figure>
</p>
<h2 id="context-website-search-and-task">Context: Website, Search, and Task<a class="anchor-link" id="context-website-search-and-task"></a></h2>
<p>The community team has a website on <strong>Hugo</strong>, an open source static site generator, hosted for free on <strong>GitHub Pages</strong>. The site has articles, events, talks, videos, and more.</p>
<blockquote>
<p>If you&rsquo;re thinking of starting your own, I recommend checking out these examples: <a href="https://blog.koehntopp.info/" target="_blank" rel="noopener noreferrer">blog.koehntopp.info</a>, <a href="https://openeverest.io/" target="_blank" rel="noopener noreferrer">openeverest.io</a>, <a href="https://perconalive.com/" target="_blank" rel="noopener noreferrer">perconalive.com</a>, <a href="https://oursqlfoundation.org/" target="_blank" rel="noopener noreferrer">oursqlfoundation.org</a></p>
</blockquote>
<p>But a Hugo site is a collection of HTML files without a backend. Search or filters only work via frontend JS or an external service. For a long time our site had no search at all. Then <strong>Kai Wagner</strong> contributed a JS search for the blog that matched exact words (<a href="https://percona.community/blog" target="_blank" rel="noopener noreferrer">percona.community/blog</a>).</p>
<p>Recently our community lead <strong>Laura Czajkowski</strong> asked for smart AI search on the site. We tried several off-the-shelf products; they were either too expensive or a poor fit. We also want search to cover more than the site itself eventually: videos from other platforms, the forum, our GitHub repos, and maybe documentation later.</p>
<p>I suggested building it ourselves. Modern AI assistants are good enough for a prototype like this. Below I&rsquo;ll explain the stack.</p>
<h2 id="what-well-do">What We&rsquo;ll Do<a class="anchor-link" id="what-well-do"></a></h2>
<p>The site stays on Hugo and GitHub Pages. The search service runs <strong>separately</strong>; for this architecture that&rsquo;s the sensible option. The goal is simple: the user types a query in plain language and gets a list of semantically relevant links.</p>
<p>Kai&rsquo;s keyword search was a step forward, but it doesn&rsquo;t catch <strong>meaning</strong>. Type &ldquo;postgresql&rdquo; and you get pages where the word appears. An article about slow queries or replication may be missing if the wording is different. <strong>Semantic search</strong> works differently: the query and documents become <strong>vectors</strong>, numeric representations of meaning (<strong>embedding</strong>). Similar meaning lands nearby in vector space even when the words differ. A query like &ldquo;how to speed up slow queries in MySQL&rdquo; can surface tuning and optimization content without those words in the title.</p>
<p>Why not another engine? <strong><a href="https://opensearch.org/" target="_blank" rel="noopener noreferrer">OpenSearch</a></strong> is a solid open-source option: full-text and vector search, mature ecosystem. I also looked at <strong><a href="https://manticoresearch.com/" target="_blank" rel="noopener noreferrer">Manticore Search</a></strong>. Both work, but <strong>semantics</strong> still need an embedding pipeline (model at index time and on each query). That&rsquo;s another service to run beside the model.</p>
<p>I wanted my own stack on <strong>Postgres</strong> with pgvector: a practical experiment, not a hunt for the perfect search product. <strong>PostgreSQL with <a href="https://github.com/pgvector/pgvector" target="_blank" rel="noopener noreferrer">pgvector</a></strong> keeps page metadata, chunks, vectors, and query history in one database. <strong><a href="https://docs.percona.com/postgresql/18/index.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL 18</a></strong> ships pgvector in the distribution; run <code>CREATE EXTENSION vector</code> and you&rsquo;re set.</p>
<p>The plan has four parts:</p>
<ol>
<li><strong>Widget</strong> on the site: search field and results (plain JS; Hugo unchanged).</li>
<li><strong>API</strong>: takes the query, embeds it with the same model as indexing, searches the DB, returns JSON links.</li>
<li><strong>Indexer</strong>: background worker that reads RSS/HTML, chunks text, embeds, writes to the DB.</li>
<li><strong>PostgreSQL + pgvector</strong>: one database for metadata, chunks, vectors, and search history.</li>
</ol>
<p>Hugo stays static; the smart parts live in a separate service. No separate vector DB, no paid embedding API, no RAG chat, only links.</p>
<p>The diagram shows two flows: <strong>search</strong> (user query) and <strong>indexing</strong> (refresh the DB on demand or on a schedule). Top to bottom, from the user:</p>
<pre class="mermaid">
flowchart TB
User(["&#128100; User"])
Widget["&#128269; JS widget<br>percona.community &middot; GitHub Pages"]
API["&#9889; FastAPI<br>search.percona.community"]
Model["&#129504; Embedding model<br>shared &middot; API &amp; indexer"]
DB[("&#128452;&#65039; PostgreSQL + pgvector")]
Content["&#128240; Content<br>blog &middot; events &middot; talks"]
Indexer["&#128229; Indexer worker"]
User --&gt;|"&#9312; query"| Widget
Widget --&gt;|"&#9313; POST /search"| API
API |embed query| Model
API |"&#9314; vector search"| DB
API --&gt;|"&#9315; results"| Widget
Widget --&gt; User
Content --&gt;|"A. RSS + HTML"| Indexer
Indexer |embed chunks| Model
Indexer --&gt;|"B. chunks + vectors"| DB
style User fill:#e1f5ff
style Widget fill:#fff4e6
style Content fill:#fff9e6
style API fill:#ffe6e6
style Model fill:#fff0f5
style Indexer fill:#f0e6ff
style DB fill:#e6ffe6
</pre>
<p>The diagram shows the shared <strong>embedding model</strong>; worth stating explicitly anyway. <strong>The indexer and the API must use the same model.</strong> Query vectors and stored vectors must share one space or search is meaningless. Don&rsquo;t mix Nomic at index time with OpenAI at query time, for example. The widget only sends text; it doesn&rsquo;t know which model runs behind the API.</p>
<p>On paper it looked simple. In practice I changed the database schema <strong>three times</strong> and tuned ranking so blog posts didn&rsquo;t crowd out events and talks. The <strong>similarity threshold</strong> mattered more than I expected: one parameter, large swing in results. Still, within a few days we had a working beta on the live site. Here&rsquo;s what shipped.</p>
<h2 id="the-result-spoiler">The Result (Spoiler)<a class="anchor-link" id="the-result-spoiler"></a></h2>
<p>It took about <strong>three unhurried days</strong> and roughly <strong>$20 in Cursor tokens</strong> to build, debug, and deploy. Try it on <strong><a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community</a></strong> (search icon in the header) or <strong><a href="https://percona.community/search/" target="_blank" rel="noopener noreferrer">percona.community/search/</a></strong>.</p>
<p>The index currently covers the site: blog, events, talks, member profiles. Video from other platforms, the forum, and GitHub are planned; the design should allow new sources without replacing the stack.</p>
<p>This is <strong>beta</strong>: the content is public and search isn&rsquo;t business-critical, but I watch stability and security.</p>
<h3 id="website-widget">Website Widget<a class="anchor-link" id="website-widget"></a></h3>
<p>The header has a search icon. Click it to get an input field and a popup with results, <strong>similarity score</strong> (0 to 1, how close the hit is in meaning), and API latency. The site stays static; the widget calls <code>search.percona.community</code> and renders JSON. &ldquo;All results&rdquo; opens <code>/search/</code>.</p>
<p>Try it on <a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community</a>, e.g. <code>slow queries mysql tuning</code> or <code>kubernetes operator database</code>. Comments welcome if something feels off.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-1-intro-pz-talks.png" alt="Smart Semantic Search using PostgreSQL and pgvector - Widget"></figure>
</p>
<h3 id="full-results-page">Full Results Page<a class="anchor-link" id="full-results-page"></a></h3>
<p>A separate <code>/search/</code> page with filters by content type, cards, and links.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-1-intro-page.png" alt="Smart Semantic Search using PostgreSQL and pgvector - Search Page"></figure>
</p>
<p><a href="https://percona.community/search/?q=Postgres+backup+solutions&amp;type=blog" target="_blank" rel="noopener noreferrer">Example</a></p>
<h3 id="api">API<a class="anchor-link" id="api"></a></h3>
<p><strong>FastAPI</strong> at <code>https://search.percona.community</code>: embed the query, search Postgres, return JSON with links, scores, and timings (model vs database).</p>
<p>The service runs on <strong>AWS EC2</strong> in Docker Compose: API, indexer, Postgres.</p>
<h3 id="demo-dashboard">Demo Dashboard<a class="anchor-link" id="demo-dashboard"></a></h3>
<p>The Cursor AI agent handled a lot of the boilerplate, so I also built a <strong>dev dashboard</strong> (<code>/demo</code>) to test search, run indexing, inspect history, and browse indexed chunks. Not for production, but it saved debugging time.</p>
<p>Demo Dashboard</p>
<figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-1-intro-demo-search.png" alt="Smart Semantic Search using PostgreSQL and pgvector - Demo Dashboard Search"></figure>

<p>Search history: making search better</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-1-intro-demo-history.png" alt="Smart Semantic Search using PostgreSQL and pgvector - Demo Dashboard Search history"></figure>
</p>
<p>Indexing status, to see when search data was last updated</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-1-intro-demo-status.png" alt="Smart Semantic Search using PostgreSQL and pgvector - Demo Dashboard Indexing status"></figure>
</p>
<p>Indexed documents with the ability to view data and chunks.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/search-part-1-intro-demo-pages.png" alt="Smart Semantic Search using PostgreSQL and pgvector - Demo Dashboard Indexed documents"></figure>
</p>
<h3 id="what-i-used">What I Used<a class="anchor-link" id="what-i-used"></a></h3>
<p>Briefly, <strong>why</strong> this stack (deeper comparison in <strong>part two</strong>):</p>
<ul>
<li>
<p><strong><a href="https://www.postgresql.org/" target="_blank" rel="noopener noreferrer">PostgreSQL</a></strong> + <strong><a href="https://github.com/pgvector/pgvector" target="_blank" rel="noopener noreferrer">pgvector</a></strong>: vectors and metadata in one DB. Cosine similarity plus an HNSW index is enough at community scale. (<a href="https://docs.percona.com/postgresql/18/enable-extensions.html#pgvector" target="_blank" rel="noopener noreferrer">pgvector in Percona docs</a>)</p>
</li>
<li>
<p><strong><a href="https://docs.percona.com/postgresql/18/index.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL 18</a></strong>: PostgreSQL with pgvector and a Docker image. Vanilla Postgres works too if you install the extension; I used Percona to try &ldquo;their&rdquo; Postgres + pgvector in a real deploy.</p>
</li>
<li>
<p><strong><a href="https://www.python.org/" target="_blank" rel="noopener noreferrer">Python</a></strong> + <strong><a href="https://fastapi.tiangolo.com/" target="_blank" rel="noopener noreferrer">FastAPI</a></strong>: fast API setup, OpenAPI included, good libraries for crawl/embed/Postgres.</p>
</li>
<li>
<p><strong><a href="https://huggingface.co/nomic-ai/nomic-embed-text-v1" target="_blank" rel="noopener noreferrer">nomic-embed-text-v1</a></strong> + <strong><a href="https://www.sbert.net/" target="_blank" rel="noopener noreferrer">sentence-transformers</a></strong>: open model, 768 dims, CPU-friendly, no per-chunk API bill. Index and query must use the <strong>same</strong> model; Nomic fits. I&rsquo;ll compare others later.</p>
</li>
<li>
<p><strong><a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a></strong> + <strong>JavaScript</strong>: thin widget on existing static site.</p>
</li>
<li>
<p><strong><a href="https://www.docker.com/" target="_blank" rel="noopener noreferrer">Docker</a></strong> / <strong>Docker Compose</strong>: same layout locally and on EC2.</p>
</li>
<li>
<p><strong><a href="https://aws.amazon.com/ec2/" target="_blank" rel="noopener noreferrer">AWS EC2</a></strong> + <strong>nginx</strong>: HTTPS on <code>search.percona.community</code>, CORS for GitHub Pages.</p>
</li>
<li>
<p><strong><a href="https://cursor.com/" target="_blank" rel="noopener noreferrer">Cursor</a></strong>: main dev tool; its AI agent helped with boilerplate, wiring API to the demo, and Docker fixes. I still reviewed everything. Without it, the same work would have taken weeks.</p>
</li>
</ul>
<h3 id="how-long-it-took">How long it took<a class="anchor-link" id="how-long-it-took"></a></h3>
<ul>
<li><strong>~6 hours</strong> with Cursor to a first prototype: crawl, API, Docker, basic demo;</li>
<li><strong>~2 more days</strong> for schema changes, per-type ranking, embed/page widget, search history, dashboard, indexer fixes, EC2 deploy;</li>
<li><strong>~$20</strong> in Cursor tokens total.</li>
</ul>
<p>Without AI I&rsquo;d have stretched the same work over weeks. With the agent I mostly wrote tasks, checked output, and fixed edges.</p>
<h3 id="about-the-code-and-repository">About the code and repository<a class="anchor-link" id="about-the-code-and-repository"></a></h3>
<p>I&rsquo;m not publishing the repo yet. The code is tied to <strong>percona.community</strong>: our RSS feeds, content types, Hugo widget, EC2 layout. It&rsquo;s an internal prototype, not a reusable library.</p>
<p>If you wanted a drop-in repo: porting someone else&rsquo;s monolith often takes longer than rebuilding from a clear sketch. Part two will have architecture, schema, and stack notes enough for a Cursor agent (or similar) to rebuild for <strong>your</strong> feeds and UI.</p>
<p>Interested in a <strong>generic open source</strong> or <strong>search-as-a-service</strong> version? Say so in the comments; I&rsquo;m weighing whether it&rsquo;s worth a separate project.</p>
<h3 id="whats-next">What&rsquo;s Next<a class="anchor-link" id="whats-next"></a></h3>
<p>Try search on <a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community</a> and comment what you find, especially where semantics beat the old substring search.</p>
<p>Part <strong>two</strong> will go inside: schema (including those three rewrites), chunking, HNSW, per-type result caps, and a local Docker Compose walkthrough.</p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/29/semantic-search-on-postgresql-part-1/">Building Smart Semantic Search using PostgreSQL and pgvector. Case Study &#8211; Part 1</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Community Server 12.3 LTS: How It Scales AI Workloads and Delivers 4x Write Performance</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/mariadb-community-server-12-3-lts-how-it-scales-ai-workloads-and-delivers-4x-write-performance/" />
      <id>https://mariadb.com/resources/blog/mariadb-community-server-12-3-lts-how-it-scales-ai-workloads-and-delivers-4x-write-performance/</id>
      <updated>2026-05-28T19:27:14+00:00</updated>
      <author><name>Ralf Gebhardt</name></author>
      <summary type="html"><![CDATA[<p>We are thrilled to announce that MariaDB Community Server 12.3 has reached Generally Available (GA) status. As our latest Long […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-community-server-12-3-lts-how-it-scales-ai-workloads-and-delivers-4x-write-performance/">MariaDB Community Server 12.3 LTS: How It Scales AI Workloads and Delivers 4x Write Performance</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>We are thrilled to announce that MariaDB Community Server 12.3 has reached Generally Available (GA) status. As our latest Long Term Maintenance (LTS) release, version 12.3 marks the culmination of the 12.x rolling release cycle (12.0&ndash;12.2), bringing together a year of rapid innovation into a rock-solid, production-ready package maintained for the next three years. From scaling modern AI&hellip;</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-community-server-12-3-lts-how-it-scales-ai-workloads-and-delivers-4x-write-performance/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-community-server-12-3-lts-how-it-scales-ai-workloads-and-delivers-4x-write-performance/">MariaDB Community Server 12.3 LTS: How It Scales AI Workloads and Delivers 4x Write Performance</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Percona Operator for PostgreSQL 3.0.0: Hard Fork, OLM Scoping, Major Upgrades</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/percona-operator-for-postgresql-3-0-0-hard-fork-olm-scoping-major-upgrades/" />
      <id>https://www.percona.com/blog/percona-operator-for-postgresql-3-0-0-hard-fork-olm-scoping-major-upgrades/</id>
      <updated>2026-05-28T13:29:03+00:00</updated>
      <author><name>Slava Sarzhan</name></author>
      <summary type="html"><![CDATA[<p>The Percona Operator for PostgreSQL 3.0.0 is here. This is the release that completes the hard fork of the operator from the Crunchy Data PostgreSQL Operator into a fully independent project, with a dedicated upstream.pgv2.percona.com API group for the inherited CRDs, an automatic CRD-rename rollout for existing 2.x installs on upgrade, and a public roadmap … Continued<br />
The post Percona Operator for PostgreSQL 3.0.0: Hard Fork, OLM Scoping, Major Upgrades appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/percona-operator-for-postgresql-3-0-0-hard-fork-olm-scoping-major-upgrades/">Percona Operator for PostgreSQL 3.0.0: Hard Fork, OLM Scoping, Major Upgrades</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><img decoding="async" loading="lazy" class="aligncenter wp-image-47941 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/hero-4-1024x375.png" alt="" width="1024" height="375"></p>
<p><span style="font-weight: 400"><br>
The <a href="https://github.com/percona/percona-postgresql-operator/tree/v3.0.0">Percona Operator for PostgreSQL 3.0.0</a> is here. This is the release that completes the hard fork of the operator from the Crunchy Data PostgreSQL Operator into a fully independent project, with a dedicated <em>upstream.pgv2.percona.com</em> API group for the inherited CRDs, an automatic CRD-rename rollout for existing 2.x installs on upgrade, and a <a href="https://github.com/orgs/percona/projects/10/views/6">public roadmap</a> that drives what comes next.<br>
</span><span style="font-weight: 400"><br>
This release ships three headline changes that matter for production teams. The <strong>CRD renaming under a Percona-owned API group</strong>, which finally lets the Crunchy operator and the Percona operator coexist in the same Kubernetes cluster. <strong>Proper OLM namespace scoping</strong>&nbsp;for OpenShift installations. And the <strong>move to the official Percona Distribution image for major PostgreSQL version upgrades</strong>, aligning the upgrade path with the same binaries that run in your clusters.</span></p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">All three land in service of the same goal: making 3.0.0 a clean, durable operational baseline for the operator&rsquo;s next several years as an independent project. Future releases will be shaped by what the community asks for and contributes back. The public roadmap is the durable signal of that commitment.</span></p>
<p><span style="font-weight: 400">In this post, you will learn about:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The hard fork and how the CRD rename unlocks coexistence with the Crunchy operator</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">OLM namespace-scoping improvements for OpenShift installations</span></li>
<li style="font-weight: 400">The move to the official Percona Distribution image for major PostgreSQL version upgrades</li>
<li style="font-weight: 400"><span style="font-weight: 400">Other improvements and the <strong>2.7.0</strong> deprecation</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Supported PostgreSQL versions and platforms</span></li>
</ul>
<p>&nbsp;</p>
<h2><b>Hard fork: CRDs renamed under </b><em><b>upstream.pgv2.percona.com</b></em><a class="anchor-link" id="hard-fork-crds-renamed-under-upstream-pgv2-percona-com"></a></h2>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-47942 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/crd-rename-1024x393.png" alt="" width="1024" height="393"></p>
<p><span style="font-weight: 400">The Percona Operator for PostgreSQL has, until now, been a soft fork. Custom Resources inherited from Crunchy PGO used the upstream </span><em><span style="font-weight: 400">postgres-operator.crunchydata.com</span></em><span style="font-weight: 400"> API group. The two operators shared CRDs, which meant you could only run one of them in a given Kubernetes cluster. Installing both would lead to overlapping CRDs, conflicting webhooks, and finalizer collisions, so platform teams had to pick a side before they had finished evaluating.</span></p>
<p><span style="font-weight: 400">Starting with <strong>3.0.0</strong>, every inherited CRD is renamed into a new dedicated </span><em><span style="font-weight: 400">upstream.pgv2.percona.com</span></em><span style="font-weight: 400"> API group (</span><a href="https://perconadev.atlassian.net/browse/K8SPG-1007"><span style="font-weight: 400">K8SPG-1007</span></a><span style="font-weight: 400">). Percona&rsquo;s own native CRDs (such as </span><span style="font-weight: 400">PerconaPGCluster</span><span style="font-weight: 400"> under </span><em><span style="font-weight: 400">pgv2.percona.com/v2</span></em><span style="font-weight: 400">) are unchanged. The change applies to the inherited resources: </span><span style="font-weight: 400">PostgresCluster</span><span style="font-weight: 400">, </span><span style="font-weight: 400">PGUpgrade</span><span style="font-weight: 400">, </span><span style="font-weight: 400">PGAdmin</span><span style="font-weight: 400">, and the rest.</span></p>
<p>&nbsp;</p>
<h3><strong>Coexistence: running both operators in the same cluster</strong><a class="anchor-link" id="coexistence-running-both-operators-in-the-same-cluster"></a></h3>
<p><span style="font-weight: 400">The practical effect is that t</span><b>he Crunchy Data PostgreSQL Operator and the Percona Operator for PostgreSQL can now run on the same Kubernetes cluster at the same time</b><span style="font-weight: 400">, even in the <strong>same</strong> namespaces, with no CRD or webhook conflict. That unlocks a few real workflows: evaluating both operators on the same staging cluster without spinning up a second cluster, running existing Crunchy-managed clusters in some namespaces while bringing up new Percona-managed clusters in others, or testing a new database version on the Percona side while production stays on Crunchy until you are confident. The choice between the two operators stops being all-or-nothing.</span></p>
<p>&nbsp;</p>
<h3><strong>Upgrade behavior for existing 2.x installs</strong><a class="anchor-link" id="upgrade-behavior-for-existing-2-x-installs"></a></h3>
<p><span style="font-weight: 400">For an existing install, the upgrade to <strong>3.0.0</strong> is mechanically simple. The operator creates the new-API-group CRDs alongside the legacy ones, then runs a one-time migration that updates dependent objects (Secrets, certificates, finalizer references) to point at the new CRD instances. Existing custom resources keep working through the legacy CRDs during the transition, and once migration completes, all reconciliation moves to the new group.</span></p>
<p><b>Old <em>PostgresCluster</em> reference:<br>
</b></p>
<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PostgresCluster
metadata:
  name: cluster1</pre>
<p><b><br>
New (after upgrade to 3.0.0):</b></p>
<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: upstream.pgv2.percona.com/v1beta1
kind: PostgresCluster
metadata:
  name: cluster1</pre>
<p>&nbsp;</p>
<p><span style="font-weight: 400">Day-to-day, your </span><em><span style="font-weight: 400">PerconaPGCluster</span></em><span style="font-weight: 400"> Custom Resource (the one most teams interact with directly) is unchanged. The rename mostly matters in three situations: when a kubectl filter or a GitOps repository hard-codes the old API group, when a CI pipeline references the legacy CRD by name, and when you run the Percona and Crunchy operators side by side and need them not to collide.</span></p>
<p><span style="font-weight: 400">Note: During the CRD migration on upgrade, the release notes report brief disruptions to pgBackRest operations (typically 1 to 2 minutes) while Kubernetes propagates certificate changes. Plan the upgrade during a maintenance window if backup continuity is critical, or pause scheduled backups during the upgrade.</span></p>
<p><span style="font-weight: 400">Full details on the API-group change are in the </span><a href="https://docs.percona.com/percona-operator-for-postgresql/latest/"><span style="font-weight: 400">Percona PostgreSQL operator documentation</span></a><span style="font-weight: 400">.</span></p>
<p>&nbsp;</p>
<h2><b>Improved OLM namespace scoping for OpenShift</b><a class="anchor-link" id="improved-olm-namespace-scoping-for-openshift"></a></h2>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-47943 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/olm-scoping-1024x393.png" alt="" width="1024" height="393"></p>
<p><span style="font-weight: 400">OpenShift users install operators through the OpenShift Lifecycle Manager (OLM), and OLM enforces an </span><em><span style="font-weight: 400">OperatorGroup</span></em><span style="font-weight: 400"> to scope which namespaces an operator watches. In practice, 2.x had quirks: teams that selected &ldquo;Single namespace&rdquo; mode would sometimes see the operator reconciling CRs in other namespaces, and teams in &ldquo;All namespaces&rdquo; mode would sometimes see incomplete coverage when CRs were created in newly-added namespaces.</span></p>
<p><span style="font-weight: 400">3.0.0 fixes this by aligning the operator&rsquo;s namespace watch list with the </span><em><span style="font-weight: 400">OperatorGroup</span></em><span style="font-weight: 400"> that OLM applies. All-namespaces installs watch all namespaces. Single-namespace installs respect the </span><span style="font-weight: 400">targetNamespaces</span><span style="font-weight: 400"> set on the </span><em><span style="font-weight: 400">OperatorGroup</span></em><span style="font-weight: 400">.</span></p>
<p>&nbsp;</p>
<h3>Why it matters in shared infrastructure<a class="anchor-link" id="why-it-matters-in-shared-infrastructure"></a></h3>
<p><span style="font-weight: 400">For an OpenShift platform team running shared infrastructure, this distinction matters operationally. A typical setup has the database operator installed once in a platform namespace (such as </span><span style="font-weight: 400">openshift-operators</span><span style="font-weight: 400">) but expected to serve </span><span style="font-weight: 400">PerconaPGCluster</span><span style="font-weight: 400"> resources owned by individual application teams in their own namespaces. If the operator over-reaches into namespaces it should not watch, RBAC noise multiplies. If it under-reaches, application teams file tickets about clusters that never reconcile. The 3.0.0 alignment with </span><em><span style="font-weight: 400">OperatorGroup</span></em><span style="font-weight: 400"> semantics removes both failure modes.</span></p>
<p>&nbsp;</p>
<h3>OperatorGroup wiring<a class="anchor-link" id="operatorgroup-wiring"></a></h3>
<p><span style="font-weight: 400">For users installing through OLM via the OpenShift web console, the install flow is unchanged. The fix is in how the operator&rsquo;s reconciler interprets the OLM-supplied namespace scope after install. For users who manage <em>OperatorGroups</em> directly, a single-namespace install looks like this:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
  name: percona-pg-operator-group
  namespace: postgres-prod
spec:
  targetNamespaces:
    - postgres-prod</pre>
<p>And an all-namespaces install:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
  name: percona-pg-operator-group
  namespace: openshift-operators
spec: {}</pre>
<p><span style="font-weight: 400">The empty </span><span style="font-weight: 400">spec: {}</span><span style="font-weight: 400"> (or an </span><span style="font-weight: 400">OperatorGroup</span><span style="font-weight: 400"> with no </span><span style="font-weight: 400">targetNamespaces</span><span style="font-weight: 400">) means &ldquo;watch all namespaces&rdquo; by OLM convention. The 3.0.0 operator now honors that.</span></p>
<p>&nbsp;</p>
<p><b><i>Note:</i></b><i><span style="font-weight: 400"> After you upgrade an existing 2.x install to 3.0.0, the operator may begin reconciling </span></i><i><span style="font-weight: 400">PerconaPGCluster</span></i><i><span style="font-weight: 400"> resources in namespaces it had previously ignored due to the prior scoping bug. Audit existing CRs across your cluster before upgrading, especially if you have stale test clusters in unintended namespaces. The release notes call this out explicitly.</span></i></p>
<p><b><i>Note for community vs certified bundle users: </i></b><i><span style="font-weight: 400">Community OLM bundles did not support cluster-wide (all-namespaces) mode in earlier versions, 3.0.0 adds it. Certified bundles already supported cluster-wide mode, but they used a separate </span></i><i><span style="font-weight: 400">stable-cw</span></i><i><span style="font-weight: 400"> channel for it with 3.0.0 the channels are unified, so users upgrading from a certified </span></i><i><span style="font-weight: 400">stable-cw</span></i><i><span style="font-weight: 400"> install need to switch their subscription channel to </span></i><i><span style="font-weight: 400">stable</span></i><i><span style="font-weight: 400"> to receive the upgrade.</span></i></p>
<p><span style="font-weight: 400">For the full install workflow on OpenShift, see the </span><a href="https://docs.percona.com/percona-operator-for-postgresql/latest/openshift.html"><span style="font-weight: 400">OpenShift installation documentation</span></a><span style="font-weight: 400">.</span></p>
<h2><span style="font-weight: 400"><b><br>
Major PostgreSQL version upgrades now use the official Percona Distribution image</b></span><a class="anchor-link" id="major-postgresql-version-upgrades-now-use-the-official-percona-distribution-image"></a></h2>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-47944 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/major-upgrade-1024x393.png" alt="" width="1024" height="393"><br>
<span style="font-weight: 400">Major-version upgrades (for example, PostgreSQL 17 to 18) require running </span><span style="font-weight: 400">pg_upgrade</span><span style="font-weight: 400">, which needs binaries for both the source and target versions in the same environment. The operator has supported major-version upgrades since 2.x, but it shipped its own dedicated upgrade image to do so. That worked, but it meant a Percona-specific image lived in the upgrade path, separate from the same Percona Distribution for PostgreSQL build that runs in your clusters.</span></p>
<p>&nbsp;</p>
<h3>Switching to the official Percona Distribution image<a class="anchor-link" id="switching-to-the-official-percona-distribution-image"></a></h3>
<p><span style="font-weight: 400">In 3.0.0, the operator switches to using the </span><b>official Percona Distribution for PostgreSQL image</b><span style="font-weight: 400"> for major-version upgrades: </span><em><span style="font-weight: 400">percona/percona-distribution-postgresql-upgrade</span></em><span style="font-weight: 400"> (current tag: </span><em><span style="font-weight: 400">18.4-17.10-16.14-15.18-14.23-1</span></em><span style="font-weight: 400">, which encodes the bundled major versions). The benefit is alignment: the binaries that run </span><span style="font-weight: 400">pg_upgrade</span><span style="font-weight: 400"> are the same binaries that ship in the corresponding </span><span style="font-weight: 400">percona-distribution-postgresql</span><span style="font-weight: 400"> image you already run in production, built from the same source, signed the same way, and patched on the same schedule. The operator orchestrates the upgrade through the </span><span style="font-weight: 400">PerconaPGUpgrade</span><span style="font-weight: 400"> Custom Resource that names the source and target versions, the upgrade image, and the target component images (PostgreSQL, pgBouncer, pgBackRest).</span></p>
<p>&nbsp;</p>
<h3>Running an upgrade through the PerconaPGUpgrade CR<a class="anchor-link" id="running-an-upgrade-through-the-perconapgupgrade-cr"></a></h3>
<p><span style="font-weight: 400">A PostgreSQL 17 to 18 upgrade looks like this:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: pgv2.percona.com/v2
kind: PerconaPGUpgrade
metadata:
  name: cluster1-17-to-18
spec:
  postgresClusterName: cluster1
  image: docker.io/percona/percona-distribution-postgresql-upgrade:18.4-17.10-16.14-15.18-14.23-1
  fromPostgresVersion: 17
  toPostgresVersion: 18
  toPostgresImage: docker.io/percona/percona-distribution-postgresql:18.4-1
  toPgBouncerImage: docker.io/percona/percona-pgbouncer:1.25.2-1
  toPgBackRestImage: docker.io/percona/percona-pgbackrest:2.58.0-2</pre>
<p><span style="font-weight: 400">Apply it with </span><em><span style="font-weight: 400">kubectl apply -f upgrade.yaml -n &lt;namespace&gt;</span></em><span style="font-weight: 400">. The operator reconciles the upgrade as a controlled, observable process: it brings the cluster down for the upgrade window, runs </span><span style="font-weight: 400">pg_upgrade</span><span style="font-weight: 400"> from the bundled image, brings the cluster back up on the target version, and updates pgBouncer and pgBackRest images in the same step.</span></p>
<p><span style="font-weight: 400">Operationally, this matters for teams running on PostgreSQL&rsquo;s annual major-version cadence. Every September brings a new major release; staying on a supported version means executing one major upgrade per cluster per year. Pulling the upgrade image from the same </span><span style="font-weight: 400">percona-distribution-postgresql</span><span style="font-weight: 400"> registry path as the runtime image means image-signature verification, mirror-to-private-registry rules, and CVE-scanning policies you already have in place apply to the upgrade flow without any per-image exception.</span></p>
<p><b><i>Note</i></b><i><span style="font-weight: 400">: The </span></i><i><span style="font-weight: 400">pgaudit</span></i><i><span style="font-weight: 400"> extension is not upgraded automatically. After the operator completes the major version upgrade, drop and recreate </span></i><i><span style="font-weight: 400">pgaudit</span></i><i><span style="font-weight: 400"> manually in each database that uses it: </span></i><i><span style="font-weight: 400">DROP EXTENSION pgaudit;</span></i><i><span style="font-weight: 400"> followed by </span></i><i><span style="font-weight: 400">CREATE EXTENSION pgaudit;</span></i><i><span style="font-weight: 400">. The release notes call this out as a required step (</span></i><a href="https://perconadev.atlassian.net/browse/K8SPG-1022"><i><span style="font-weight: 400">K8SPG-1022</span></i></a><i><span style="font-weight: 400">). Also worth scanning for collation-dependent indexes after the upgrade and refreshing collation metadata with </span></i><i><span style="font-weight: 400">ALTER DATABASE &lt;name&gt; REFRESH COLLATION VERSION;</span></i><i><span style="font-weight: 400"> per the upstream PostgreSQL 18 release notes.</span></i></p>
<p><span style="font-weight: 400">Full procedure, prerequisites, and rollback notes are in the </span><a href="https://docs.percona.com/percona-operator-for-postgresql/latest/update-db-major.html"><span style="font-weight: 400">major version upgrade documentation</span></a><span style="font-weight: 400">.</span></p>
<h2><b>Other Improvements</b><a class="anchor-link" id="other-improvements"></a></h2>
<p><span style="font-weight: 400">Operational polish landed alongside the headline changes:</span></p>
<ul>
<li style="font-weight: 400"><b>Go 1.26 </b><span style="font-weight: 400">update (</span><a href="https://perconadev.atlassian.net/browse/K8SPG-1019"><span style="font-weight: 400">K8SPG-1019</span></a><span style="font-weight: 400">): the operator binary is now built with Go 1.26, picking up performance optimizations, tooling improvements, and the security fixes that landed in the Go runtime since the previous release.</span></li>
<li style="font-weight: 400"><b>pgaudit upgrade documentation </b><span style="font-weight: 400">(</span><a href="https://perconadev.atlassian.net/browse/K8SPG-1022"><span style="font-weight: 400">K8SPG-1022</span></a><span style="font-weight: 400">): the major-version upgrade docs now include an explicit pgaudit drop-and-recreate procedure, surfacing the gotcha that previously caught users mid-upgrade.</span></li>
</ul>
<p><span style="font-weight: 400">The release also defaults the cluster-upgrade documentation to PostgreSQL 18 across all examples and tutorials.</span></p>
<p>&nbsp;</p>
<h3><b>Supported software and platforms</b><a class="anchor-link" id="supported-software-and-platforms"></a></h3>
<p><span style="font-weight: 400">The Percona Operator for PostgreSQL 3.0.0 is developed and tested on:</span></p>
<ul>
<li style="font-weight: 400"><b>PostgreSQL: </b><span style="font-weight: 400">14.23-1, 15.18-1, 16.14-1, 17.10-1, 18.4-1&nbsp;</span></li>
<li style="font-weight: 400"><b>pgBackRest:</b><span style="font-weight: 400"> 2.58.0-2</span></li>
<li style="font-weight: 400"><b>pgBouncer: </b><span style="font-weight: 400">1.25.2-1</span></li>
<li style="font-weight: 400"><b>Patroni:</b><span style="font-weight: 400"> 4.1.3</span></li>
<li style="font-weight: 400"><b>PostGIS:</b><span style="font-weight: 400"> 3.5.6</span></li>
<li style="font-weight: 400"><b>PMM Client:</b> 2.44.1-1 and 3.7.1</li>
</ul>
<p>&nbsp;</p>
<h3><b>Supported Kubernetes platforms:</b><a class="anchor-link" id="supported-kubernetes-platforms"></a></h3>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Google Kubernetes Engine (GKE) 1.33 to 1.35</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Amazon Elastic Kubernetes Service (EKS) 1.33 to 1.35</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">OpenShift 4.18 to 4.21</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Azure Kubernetes Service (AKS) 1.33 to 1.35</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Minikube 1.38.1 (Kubernetes v1.35.1) for local development</span></li>
</ul>
<p>&nbsp;</p>
<h3><b>Deprecation: 2.7.0 support dropped</b><a class="anchor-link" id="deprecation-2-7-0-support-dropped"></a></h3>
<p><span style="font-weight: 400">Support for Custom Resource Definitions from operator version 2.7.0 has been removed. If you are still on 2.7.0, upgrade to 2.8.x or 2.9.x first, then upgrade to 3.0.0. The CRD migration described above only handles 2.8.x and 2.9.x to 3.0.0 transitions cleanly.</span></p>
<p>&nbsp;</p>
<h2><b>Conclusion</b><a class="anchor-link" id="conclusion"></a></h2>
<p><span style="font-weight: 400">3.0.0 is the release where the Percona Operator for PostgreSQL becomes a fully independent project. The CRD rename removes the last upstream coupling that mattered operationally. The OLM scoping fix removes a long-standing OpenShift quirk. The official major-version upgrade image removes one of the more painful operational gaps in earlier versions.</span></p>
<p><span style="font-weight: 400">Beyond the technical work, 3.0.0 is also where Percona&rsquo;s commitment to community-driven development moves from intent to mechanism. The </span><a href="https://github.com/orgs/percona/projects/10/views/6"><span style="font-weight: 400">public roadmap</span></a><span style="font-weight: 400"> is open. The </span><a href="https://github.com/percona/percona-postgresql-operator/issues"><span style="font-weight: 400">issue tracker</span></a><span style="font-weight: 400"> is open. The images are freely redistributable. Future releases will be shaped by what the community asks for, files, and contributes back. If there is a feature you want to see in 3.1.0 or 3.2.0, open an issue or a PR, that is where the work happens now.</span></p>
<p>&nbsp;</p>
<h2><b>Try It Out</b><a class="anchor-link" id="try-it-out"></a></h2>
<ul>
<li style="font-weight: 400"><b>Release notes:</b> <a href="https://docs.percona.com/percona-operator-for-postgresql/latest/ReleaseNotes/Kubernetes-Operator-for-PostgreSQL-RN3.0.0.html"><span style="font-weight: 400">Percona Operator for PostgreSQL 3.0.0 Release Notes</span></a></li>
<li style="font-weight: 400"><b>Documentation:</b> <a href="https://docs.percona.com/percona-operator-for-postgresql/latest/"><span style="font-weight: 400">https://docs.percona.com/percona-operator-for-postgresql/latest/</span></a></li>
<li style="font-weight: 400"><b>GitHub:</b> <a href="https://github.com/percona/percona-postgresql-operator"><span style="font-weight: 400">percona/percona-postgresql-operator</span></a></li>
<li style="font-weight: 400"><b>Public roadmap: </b><a href="https://github.com/orgs/percona/projects/10/views/6"><span style="font-weight: 400">https://github.com/orgs/percona/projects/10/views/6</span></a></li>
<li style="font-weight: 400"><b>Issue tracker:</b> <a href="https://github.com/percona/percona-postgresql-operator/issues"><span style="font-weight: 400">https://github.com/percona/percona-postgresql-operator/issues</span></a></li>
<li style="font-weight: 400"><b>Community Forum: </b><a href="https://forums.percona.com/c/postgresql/percona-kubernetes-operator-for-postgresql/68">forums.percona.com</a></li>
</ul>
<p>The post <a href="https://www.percona.com/blog/percona-operator-for-postgresql-3-0-0-hard-fork-olm-scoping-major-upgrades/">Percona Operator for PostgreSQL 3.0.0: Hard Fork, OLM Scoping, Major Upgrades</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/percona-operator-for-postgresql-3-0-0-hard-fork-olm-scoping-major-upgrades/">Percona Operator for PostgreSQL 3.0.0: Hard Fork, OLM Scoping, Major Upgrades</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Foundation at Oracle’s MySQL Contributor Summit: Ecosystems, Forks and Constructive Coexistence</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-foundation-at-oracles-mysql-contributor-summit/" />
      <id>https://mariadb.org/mariadb-foundation-at-oracles-mysql-contributor-summit/</id>
      <updated>2026-05-28T12:33:27+00:00</updated>
      <author><name>Kaj Arnö</name></author>
      <summary type="html"><![CDATA[<p>Last week, Oracle invited MariaDB Foundation to give a presentation at Oracle’s MySQL Contributor Summit 2026. I had the opportunity to participate remotely and speak about MariaDB’s role within the broader MySQL ecosystem. …<br />
Continue reading \"MariaDB Foundation at Oracle’s MySQL Contributor Summit: Ecosystems, Forks and Constructive Coexistence\"<br />
The post MariaDB Foundation at Oracle’s MySQL Contributor Summit: Ecosystems, Forks and Constructive Coexistence appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-at-oracles-mysql-contributor-summit/">MariaDB Foundation at Oracle’s MySQL Contributor Summit: Ecosystems, Forks and Constructive Coexistence</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Last week, Oracle invited MariaDB Foundation to give a presentation at Oracle&rsquo;s MySQL Contributor Summit 2026. I had the opportunity to participate remotely and speak about MariaDB&rsquo;s role within the broader MySQL ecosystem. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-foundation-at-oracles-mysql-contributor-summit/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Foundation at Oracle&rsquo;s MySQL Contributor Summit: Ecosystems, Forks and Constructive Coexistence&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-at-oracles-mysql-contributor-summit/">MariaDB Foundation at Oracle&rsquo;s MySQL Contributor Summit: Ecosystems, Forks and Constructive Coexistence</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-at-oracles-mysql-contributor-summit/">MariaDB Foundation at Oracle’s MySQL Contributor Summit: Ecosystems, Forks and Constructive Coexistence</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Community Server Q2 2026 corrective releases</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/mariadb-community-server-q2-2026-corrective-releases/" />
      <id>https://mariadb.com/resources/blog/mariadb-community-server-q2-2026-corrective-releases/</id>
      <updated>2026-05-27T19:09:10+00:00</updated>
      <author><name>Daniel Bartholomew</name></author>
      <summary type="html"><![CDATA[<p>MariaDB is pleased to announce the immediate availability of MariaDB Community Server 11.8.8, 11.4.12, 10.11.18, and 10.6.27 corrective releases. See […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-community-server-q2-2026-corrective-releases/">MariaDB Community Server Q2 2026 corrective releases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB is pleased to announce the immediate availability of MariaDB Community Server 11.8.8, 11.4.12, 10.11.18, and 10.6.27 corrective releases. See the release notes and changelogs for additional details on each release and visit mariadb.com/downloads to download.</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-community-server-q2-2026-corrective-releases/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-community-server-q2-2026-corrective-releases/">MariaDB Community Server Q2 2026 corrective releases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Migrate from Crunchy Data PostgreSQL Operator to Percona PostgreSQL Operator: Backup-Restore and PV Reuse</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/migrate-from-crunchy-data-to-percona-postgresql-operator-backup-restore-pv-reuse/" />
      <id>https://www.percona.com/blog/migrate-from-crunchy-data-to-percona-postgresql-operator-backup-restore-pv-reuse/</id>
      <updated>2026-05-27T12:02:55+00:00</updated>
      <author><name>Slava Sarzhan</name></author>
      <summary type="html"><![CDATA[<p>A Percona PostgreSQL operator pgBackRest restore is the simplest way to move off the Crunchy Data PostgreSQL Operator: take a full Crunchy backup, point the new Percona cluster’s dataSource at the existing pgBackRest archive, and the cluster bootstraps from it before its first start. This post covers that path, plus a second option, persistent-volume reuse, for cases … Continued<br />
The post Migrate from Crunchy Data PostgreSQL Operator to Percona PostgreSQL Operator: Backup-Restore and PV Reuse appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/migrate-from-crunchy-data-to-percona-postgresql-operator-backup-restore-pv-reuse/">Migrate from Crunchy Data PostgreSQL Operator to Percona PostgreSQL Operator: Backup-Restore and PV Reuse</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><img decoding="async" loading="lazy" class="aligncenter wp-image-47145 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/hero-3-1024x375.png" alt="" width="1024" height="375"><br>
A Percona PostgreSQL operator pgBackRest restore is the simplest way to move off the Crunchy Data PostgreSQL Operator: take a full Crunchy backup, point the new Percona cluster&rsquo;s&nbsp;<code>dataSource</code>&nbsp;at the existing pgBackRest archive, and the cluster bootstraps from it before its first start. This post covers that path, plus a second option, persistent-volume reuse, for cases where you want to skip the data copy entirely.</p>
<p>This is&nbsp;<strong>part 3 of a 3-part series</strong>&nbsp;on running PostgreSQL on Kubernetes with a fully open-source operator.&nbsp;<a href="https://www.percona.com/blog/open-source-postgresql-operator-kubernetes-licensing-landscape/" rel="nofollow">Part 1</a>&nbsp;walked through the changing open-source landscape and announced the <strong>hard fork</strong> of the Crunchy Data PostgreSQL Operator into the fully independent <a href="https://github.com/percona/percona-postgresql-operator/tree/v3.0.0"><strong>Percona PostgreSQL Operator v3.0.0</strong></a>.&nbsp;<a href="https://www.percona.com/blog/migrate-from-crunchy-data-to-percona-postgresql-operator-standby-cluster" rel="nofollow">Part 2</a>&nbsp;covered the&nbsp;<strong>standby cluster</strong>&nbsp;method, the safest migration path when downtime budget is tight.</p>
<p>This post covers two simpler paths:</p>
<ul>
<li><strong>Backup and restore</strong>, the fastest if you can tolerate a short application-downtime window</li>
<li><strong>Persistent volume reuse</strong>, when you want to skip the data copy entirely and keep the existing PGDATA</li>
</ul>
<p>If you are landing here cold,&nbsp;<a href="https://www.percona.com/blog/not-all-open-source-is-equal-choosing-postgresql-operator-kubernetes-2026/" rel="nofollow">start with part 1</a> for the why, then read&nbsp;<a href="https://www.percona.com/blog/migrate-from-crunchy-data-to-percona-postgresql-operator-standby-cluster" rel="nofollow">Part 2</a>&nbsp;for the standby method. The rest of this post assumes you have already decided to migrate and want a tested playbook.</p>
<div class="markdown-heading">
<h3 class="heading-element">Tested with<a class="anchor-link" id="tested-with"></a></h3>
</div>
<table>
<thead>
<tr>
<th>Component</th>
<th>Version</th>
</tr>
</thead>
<tbody>
<tr>
<td>Crunchy Data PostgreSQL Kubernetes Operator</td>
<td>v5.8.x (tested on v5.8.7)</td>
</tr>
<tr>
<td>Percona PostgreSQL Kubernetes Operator</td>
<td>v3.x.x (tested on v3.0.0)</td>
</tr>
<tr>
<td>PostgreSQL</td>
<td>18 (must match between source and target)</td>
</tr>
<tr>
<td>Object storage</td>
<td>SeaweedFS (Apache-2.0), or any S3-compatible service. Required for the backup-and-restore method, optional for PV reuse.</td>
</tr>
<tr>
<td>Tools</td>
<td><code>kubectl</code>,&nbsp;<code>helm</code>&nbsp;(v3)</td>
</tr>
</tbody>
</table>
<p>Different versions may have slight differences in CR fields or behavior. Always consult the official documentation for the operator and PostgreSQL version you are running.</p>
<p>&nbsp;</p>
<div class="markdown-heading">
<h3 class="heading-element">What this post does NOT cover<a class="anchor-link" id="what-this-post-does-not-cover"></a></h3>
</div>
<ul>
<li>Application-side connection-string changes beyond updating to the new pgBouncer service</li>
<li>Schema-changing upgrades, major PostgreSQL version upgrades, or extension migrations</li>
<li>Crunchy enterprise-only features like TDE or pgBackRest custom encryption</li>
<li>Operating two operators against the same namespace before the hard fork. Use Percona PostgreSQL Operator v3.0.0 or higher.</li>
</ul>
<p>&nbsp;</p>
<div class="markdown-heading">
<h2 class="heading-element">1. Migration using backup and restore<a class="anchor-link" id="1-migration-using-backup-and-restore"></a></h2>
</div>
<p>This is often the fastest and simplest path, especially when you do not need a live standby. You take a full backup of the Crunchy source cluster, then create a Percona cluster that automatically restores from that backup before its first start.</p>
<p>Data written between the final backup and the application cutover is lost, so the migration window is the time between those two events. For a near-zero-downtime alternative, see&nbsp;<a href="https://www.percona.com/blog/migrate-from-crunchy-data-to-percona-postgresql-operator-standby-cluster/" rel="nofollow">part 2: standby cluster method</a>.</p>
<div class="markdown-heading">
<p>&nbsp;</p>
<h3 class="heading-element">Overview<br>
<img decoding="async" loading="lazy" class="aligncenter wp-image-47147 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/backup-restore-1024x393.png" alt="" width="1024" height="393"><a class="anchor-link" id="overview"></a></h3>
<div class="markdown-heading">
&nbsp;
<h3 class="heading-element">Before you begin<a class="anchor-link" id="before-you-begin"></a></h3>
</div>
<p>Set the namespace once. Every command in this guide reads from this variable:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">export MIGRATION_NS=postgres-migration
kubectl create namespace $MIGRATION_NS</pre>

<div class="markdown-heading">
<p>&nbsp;</p>
<h3 class="heading-element">Deploy SeaweedFS<a class="anchor-link" id="deploy-seaweedfs"></a></h3>
</div>
<p>Skip this step if you already have an S3-compatible repository (AWS S3, GCS, Ceph). Update the endpoint and credentials in the YAML examples accordingly.</p>
<p>SeaweedFS provides an S3-compatible object store that runs inside Kubernetes. Both operators will use it as the shared pgBackRest WAL archive.</p>
<p><strong>TLS is required.</strong>&nbsp;pgBackRest always connects to S3 endpoints over HTTPS, even when&nbsp;<code>repo1-s3-verify-tls: "n"</code>&nbsp;is set (that flag skips certificate verification, it does not fall back to HTTP). The steps below generate a self-signed certificate and pass it to SeaweedFS via Helm values.</p>
<pre class="urvanov-syntax-highlighter-plain-tag"># Generate a self-signed TLS certificate for SeaweedFS S3
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 
  -keyout /tmp/seaweedfs.key 
  -out /tmp/seaweedfs.crt 
  -subj "/CN=seaweedfs-all-in-one"

kubectl -n $MIGRATION_NS create secret tls seaweedfs-s3-tls 
  --cert=/tmp/seaweedfs.crt 
  --key=/tmp/seaweedfs.key

helm repo add seaweedfs https://seaweedfs.github.io/seaweedfs/helm
helm repo update

helm install seaweedfs seaweedfs/seaweedfs 
  --namespace $MIGRATION_NS 
  --version 4.23.0 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-backup-restore/examples/seaweedfs-values.yaml 
  --wait</pre>
<p>The Helm values file in the repo creates the&nbsp;<code>pg-migration</code>&nbsp;bucket on first start, so no separate&nbsp;<code>aws s3 mb</code>&nbsp;step is needed.</p>
<div class="markdown-heading">
&nbsp;
<h3 class="heading-element">Step 0. Create pgBackRest secrets<a class="anchor-link" id="step-0-create-pgbackrest-secrets"></a></h3>
</div>
<p>Both operators need credentials to read and write the shared SeaweedFS bucket. Apply the secrets from&nbsp;<a href="https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e"><code>examples/01-pgbackrest-secrets.yaml</code></a>:</p>
<pre class="urvanov-syntax-highlighter-plain-tag"># Copy and edit the file first to set your credentials.
kubectl apply -n $MIGRATION_NS 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-backup-restore/examples/01-pgbackrest-secrets.yaml</pre>
<p>Both contain the same SeaweedFS credentials (<em><code>pgmigration</code></em>&nbsp;/&nbsp;<em><code>pgmigration123</code></em>). For AWS S3, replace those with your IAM access key ID and secret access key.</p>
<p>&nbsp;</p>
<div class="markdown-heading">
<h3 class="heading-element">Step 1. Start with your existing Crunchy Data cluster<a class="anchor-link" id="step-1-start-with-your-existing-crunchy-data-cluster"></a></h3>
</div>
<p>If you already have a running Crunchy cluster, ensure its pgBackRest&nbsp;<code><em>repo1</em></code>&nbsp;points at the shared bucket. The&nbsp;<em><code>repo1-path</code></em>&nbsp;value must match the path that will be referenced in the Percona&nbsp;<em><code>dataSource.pgbackrest.global.repo1-path</code></em>&nbsp;field.</p>
<p>Optional: deploy the Crunchy operator for testing.&nbsp;<strong>The Helm install below is shown only as a quick way to reproduce this blog post&rsquo;s example. The migration steps in the rest of this post do not depend on how you deployed the source operator.</strong></p>
<pre class="urvanov-syntax-highlighter-plain-tag">helm install pgo 
  oci://registry.developers.crunchydata.com/crunchydata/pgo 
  -n $MIGRATION_NS 
  --version 5.8.7 
  --set singleNamespace=true 
  --wait</pre>
<p>To start a fresh source cluster for testing, apply&nbsp;<a href="https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-backup-restore/examples/02-crunchy-source-cluster.yaml"><code>examples/02-crunchy-source-cluster.yaml</code></a>:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl apply -n $MIGRATION_NS 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-backup-restore/examples/02-crunchy-source-cluster.yaml</pre>
<p>The key pgBackRest settings:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">global:
  repo1-path: /crunchy-to-percona/repo1   # source repo referenced in Percona dataSource
  repo1-s3-uri-style: path                # required for path-style S3 endpoints (SeaweedFS, MinIO)
  repo1-s3-verify-tls: "n"                # skip TLS verification for self-signed cert; remove for AWS S3
repos:
  - name: repo1
    s3:
      bucket: pg-migration
      endpoint: seaweedfs-all-in-one.postgres-migration.svc.cluster.local:8443
      region: us-east-1</pre>
<p>Wait for the cluster and its pgBackRest stanza to be ready:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl wait pod 
  --selector postgres-operator.crunchydata.com/cluster=crunchy-source,postgres-operator.crunchydata.com/data=postgres 
  -n $MIGRATION_NS 
  --for=condition=Ready 
  --timeout=300s

kubectl wait postgrescluster/crunchy-source 
  -n $MIGRATION_NS 
  --for=jsonpath='{.status.pgbackrest.repos[0].stanzaCreated}'=true 
  --timeout=300s</pre>
<p>&nbsp;</p>
<h3>Step 2. Trigger a full backup (the migration cutover point)<a class="anchor-link" id="step-2-trigger-a-full-backup-the-migration-cutover-point"></a></h3>
<p>This is the backup the Percona cluster will restore from. Stop accepting writes on the application side before triggering it to ensure a consistent snapshot, or accept that data written after this backup will be lost.</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl annotate postgrescluster crunchy-source 
  -n $MIGRATION_NS 
  postgres-operator.crunchydata.com/pgbackrest-backup="$(date +%s)"

kubectl wait job 
  --selector postgres-operator.crunchydata.com/pgbackrest-backup=manual,postgres-operator.crunchydata.com/cluster=crunchy-source 
  -n $MIGRATION_NS 
  --for=condition=Complete 
  --timeout=600s</pre>
<p>&nbsp;</p>
<div class="markdown-heading">
<h3 class="heading-element">Step 3. Deploy the Percona Operator<a class="anchor-link" id="step-3-deploy-the-percona-operator"></a></h3>
</div>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl apply -n $MIGRATION_NS --server-side 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/tags/v3.0.0/deploy/bundle.yaml

kubectl wait deployment percona-postgresql-operator 
  -n $MIGRATION_NS 
  --for=condition=Available 
  --timeout=120s</pre>

<div class="markdown-heading">
&nbsp;
<h3 class="heading-element">Step 4. Create the Percona cluster from the backup<a class="anchor-link" id="step-4-create-the-percona-cluster-from-the-backup"></a></h3>
</div>
<p>Apply&nbsp;<a href="https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-backup-restore/examples/03-percona-restored-cluster.yaml"><code>examples/03-percona-restored-cluster.yaml</code></a>:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl apply -n $MIGRATION_NS 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-backup-restore/examples/03-percona-restored-cluster.yaml</pre>
<p>The key section that bootstraps the cluster from the Crunchy backup:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">dataSource:
  pgbackrest:
    stanza: db
    configuration:
      - secret:
          name: percona-pgbackrest-secret
    global:
      # Must match repo1-path in the Crunchy source cluster exactly.
      repo1-path: /crunchy-to-percona/repo1
      repo1-s3-uri-style: path
      repo1-s3-verify-tls: "n"
    repo:
      name: repo1
      s3:
        bucket: pg-migration
        endpoint: seaweedfs-all-in-one.postgres-migration.svc.cluster.local:8443
        region: us-east-1</pre>
<p>The Percona cluster&rsquo;s own backup repository must use a different path from the Crunchy source:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">backups:
  pgbackrest:
    global:
      repo1-path: /percona-restored/repo1   # different from Crunchy's path</pre>
<p>As soon as the Custom Resource is applied, the cluster is bootstrapped from the storage referenced in&nbsp;<em><code>dataSource</code></em>&nbsp;and then started. Once the cluster becomes ready, you can immediately create new backups; in this case,&nbsp;<em><code>repo1</code></em>&nbsp;from the&nbsp;<em><code>backups</code></em>&nbsp;section will be used as the target repository.</p>
<p>Wait for the cluster to reach ready state:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl wait perconapgcluster/percona-restored 
  -n $MIGRATION_NS 
  --for=jsonpath='{.status.state}'=ready 
  --timeout=600s</pre>
<p>Verify the data was restored successfully:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">PERCONA_PRIMARY=$(kubectl get pod -n $MIGRATION_NS 
  --selector postgres-operator.crunchydata.com/cluster=percona-restored,postgres-operator.crunchydata.com/role=primary 
  -o jsonpath='{.items[0].metadata.name}')

kubectl -n $MIGRATION_NS exec "${PERCONA_PRIMARY}" -c database -- 
  psql -t -c "SELECT pg_is_in_recovery();"</pre>
<p>Expected output:&nbsp;<em><code>f</code></em>. The cluster is the primary and accepts writes.<br>
&nbsp;</p>
<h3 class="heading-element">Step 5. Verify the cluster is healthy<a class="anchor-link" id="step-5-verify-the-cluster-is-healthy"></a></h3>

<pre class="urvanov-syntax-highlighter-plain-tag">kubectl wait perconapgcluster/percona-restored 
  -n $MIGRATION_NS 
  --for=jsonpath='{.status.state}'=ready 
  --timeout=600s

kubectl wait perconapgcluster/percona-restored 
  -n $MIGRATION_NS 
  --for=jsonpath='{.status.pgbackrest.repos[0].stanzaCreated}'=true 
  --timeout=300s</pre>

<div class="markdown-heading">
&nbsp;
<h3 class="heading-element">Step 6. Take a post-migration backup<a class="anchor-link" id="step-6-take-a-post-migration-backup"></a></h3>
</div>
<p>Apply&nbsp;<a href="https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-backup-restore/examples/04-post-migration-backup.yaml"><code>examples/04-post-migration-backup.yaml</code></a>:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl apply -n $MIGRATION_NS 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-backup-restore/examples/04-post-migration-backup.yaml

kubectl wait perconapgbackup/post-migration-backup 
  -n $MIGRATION_NS 
  --for=jsonpath='{.status.state}'=Succeeded 
  --timeout=600s</pre>
<p>This creates a clean recovery baseline on the Percona cluster&rsquo;s own repository. All future PITR restores will use this backup, independent of the Crunchy archive.<br>
&nbsp;</p>
<h3 class="heading-element">Step 7. Reconnect your application<a class="anchor-link" id="step-7-reconnect-your-application"></a></h3>

<pre class="urvanov-syntax-highlighter-plain-tag">kubectl get service -n $MIGRATION_NS 
  --selector postgres-operator.crunchydata.com/cluster=percona-restored,postgres-operator.crunchydata.com/role=pgbouncer</pre>

<div class="markdown-heading">
&nbsp;
<h3 class="heading-element">Step 8. Clean up the Crunchy cluster<a class="anchor-link" id="step-8-clean-up-the-crunchy-cluster"></a></h3>
</div>
<p>Once the migration is verified and your application is connected to the new cluster:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl delete postgrescluster crunchy-source -n $MIGRATION_NS
helm uninstall pgo -n $MIGRATION_NS</pre>
<p>&nbsp;</p>
<div class="markdown-heading">
<h3 class="heading-element">Rollback<a class="anchor-link" id="rollback"></a></h3>
</div>
<p>Until Step 8, rollback is straightforward: switch the application connection string back to the Crunchy pgBouncer service. The Crunchy primary still holds the authoritative state because no writes were directed at the Percona cluster during the cutover (you stopped writes before Step 2). Any writes the application sent to the Percona cluster after cutover will not be present on Crunchy and would need to be replayed manually.</p>
<p>After Step 8, rollback requires restoring the Crunchy cluster from a backup, which is feasible because the original&nbsp;<em><code>repo1</code></em>&nbsp;is still in the bucket.</p>
<div class="markdown-heading">
&nbsp;
<h3 class="heading-element">Troubleshooting<a class="anchor-link" id="troubleshooting"></a></h3>
</div>
<p><strong><code>archive.info missing</code>.</strong>&nbsp;The&nbsp;<code><em>repo1-path</em></code>&nbsp;in&nbsp;<em><code>dataSource.pgbackrest.global</code></em>&nbsp;must match the Crunchy source cluster&rsquo;s&nbsp;<code>repo1-path</code>&nbsp;exactly:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl get postgrescluster crunchy-source -n $MIGRATION_NS 
  -o jsonpath='{.spec.backups.pgbackrest.global.repo1-path}'

kubectl get perconapgcluster percona-restored -n $MIGRATION_NS 
  -o jsonpath='{.spec.dataSource.pgbackrest.global.repo1-path}'</pre>
<p>&nbsp;</p>
<p><strong>Restore job fails with TLS errors.&nbsp;</strong>pgBackRest requires HTTPS even with&nbsp;<em><code>repo1-s3-verify-tls: "n"</code></em>. Verify SeaweedFS is reachable:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl run -i --rm s3-check 
  --image=perconalab/awscli 
  --restart=Never 
  -n $MIGRATION_NS 
  -- bash -c "
    AWS_ACCESS_KEY_ID=pgmigration 
    AWS_SECRET_ACCESS_KEY=pgmigration123 
    AWS_DEFAULT_REGION=us-east-1 
    aws --endpoint-url https://seaweedfs-all-in-one.${MIGRATION_NS}.svc.cluster.local:8443 
        --no-verify-ssl 
        s3 ls s3://pg-migration
  "</pre>
<p>&nbsp;</p>
<p><strong>Cluster stuck in restoring state.</strong>&nbsp;Check the pgBackRest restore job logs:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl logs 
  --selector postgres-operator.crunchydata.com/cluster=percona-restored,postgres-operator.crunchydata.com/pgbackrest-restore=percona-restored 
  -n $MIGRATION_NS 
  -c pgbackrest</pre>
<p><strong>Data missing after restore.</strong> The restore captures data up to the latest backup. If post-backup data is critical, re-run the backup on the Crunchy cluster after quiescing writes, then delete and recreate the Percona cluster to restore from the newer backup.</p>
<div class="markdown-heading">
&nbsp;
<h2 class="heading-element">2. Migration using existing persistent volumes<a class="anchor-link" id="2-migration-using-existing-persistent-volumes"></a></h2>
</div>
<p>This method reuses the Crunchy primary&rsquo;s PGDATA persistent volume directly. It avoids a full backup-restore cycle: you retain the Crunchy primary&rsquo;s PV, delete the Crunchy cluster, then create a Percona cluster whose PVC binds to that same PV. PostgreSQL starts on the existing data directory without any restore step.</p>
<p>It is useful when:</p>
<ul>
<li>you want to avoid copying data</li>
<li>your storage is very large</li>
<li>you must preserve the original data directory exactly</li>
<li>you removed the cluster but kept the PV</li>
</ul>
<p>&nbsp;</p>
<div class="markdown-heading">
<h3 class="heading-element">Overview<img decoding="async" loading="lazy" class="aligncenter wp-image-47148 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/pv-reuse-1024x393.png" alt="" width="1024" height="393"><a class="anchor-link" id="overview"></a></h3>
<p>&nbsp;</p>
<h3 class="heading-element">Before you begin<a class="anchor-link" id="before-you-begin"></a></h3>

<pre class="urvanov-syntax-highlighter-plain-tag">export MIGRATION_NS=postgres-migration
kubectl create namespace $MIGRATION_NS</pre>

<div class="markdown-heading">
&nbsp;
<h3 class="heading-element">Step 1. Deploy the Crunchy and Percona operators<a class="anchor-link" id="step-1-deploy-the-crunchy-and-percona-operators"></a></h3>
</div>
<p>Both operators run in the same namespace. Crunchy PGO is uninstalled during the migration once the PV is retained.</p>
<blockquote>
<p><strong>Note (Crunchy)</strong>: The Helm install for Crunchy PGO below is shown only as a quick way to reproduce this blog post&rsquo;s example. If you are running Crunchy PGO in production, follow&nbsp;<a href="https://access.crunchydata.com/documentation/postgres-operator/latest/installation/" rel="nofollow">the official Crunchy Data documentation</a>&nbsp;for installation. The migration steps in the rest of this post do not depend on how you deployed the source operator.</p>
</blockquote>
<blockquote>
<p><strong>Note (Percona)</strong>: The&nbsp;<em><code>kubectl apply</code></em> of the Percona operator below uses defult configuration of <em><code>v3.0.0</code></em>&nbsp;from the operator repo for reproducibility of this guide. For production deployments, follow&nbsp;<a href="https://docs.percona.com/percona-operator-for-postgresql/latest/gke.html" rel="nofollow">the official Percona Operator for PostgreSQL installation documentation</a> to ensure the cluster configuration is properly sized and configured for your workload and traffic requirements.</p>
</blockquote>
<pre class="urvanov-syntax-highlighter-plain-tag">helm install pgo 
  oci://registry.developers.crunchydata.com/crunchydata/pgo 
  -n $MIGRATION_NS 
  --version 5.8.7 
  --set singleNamespace=true 
  --wait

kubectl apply -n $MIGRATION_NS --server-side 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/tags/v3.0.0/deploy/bundle.yaml

kubectl wait deployment pgo 
  -n $MIGRATION_NS --for=condition=Available --timeout=120s

kubectl wait deployment percona-postgresql-operator 
  -n $MIGRATION_NS --for=condition=Available --timeout=120s</pre>

<div class="markdown-heading">
&nbsp;
<h3 class="heading-element">Step 2. Start the Crunchy source cluster<a class="anchor-link" id="step-2-start-the-crunchy-source-cluster"></a></h3>
</div>
<p>If you already have a running Crunchy cluster with&nbsp;<code><em>replicas: 1</em></code>, proceed to Step 3.</p>
<p>To start a fresh cluster for testing:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl apply -n $MIGRATION_NS 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-pv/examples/01-crunchy-source-cluster.yaml

kubectl wait pod 
  --selector postgres-operator.crunchydata.com/cluster=crunchy-source,postgres-operator.crunchydata.com/role=master 
  -n $MIGRATION_NS 
  --for=condition=Ready 
  --timeout=300s</pre>

<div class="markdown-heading">
&nbsp;
<h3 class="heading-element">Step 3. Stop writes and identify the primary PV<a class="anchor-link" id="step-3-stop-writes-and-identify-the-primary-pv"></a></h3>
</div>
<p>Stop your application from writing to the database. This is the start of the downtime window. Then identify the primary pod, its PVC, and the backing PV:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">PRIMARY=$(kubectl get pod -n $MIGRATION_NS 
  --selector postgres-operator.crunchydata.com/cluster=crunchy-source,postgres-operator.crunchydata.com/role=master 
  -o jsonpath='{.items[0].metadata.name}')

PVC_NAME=$(kubectl get pod -n $MIGRATION_NS "${PRIMARY}" 
  -o jsonpath='{.spec.volumes[?(@.name=="postgres-data")].persistentVolumeClaim.claimName}')

PV_NAME=$(kubectl get pvc -n $MIGRATION_NS "${PVC_NAME}" 
  -o jsonpath='{.spec.volumeName}')

echo "Primary pod: ${PRIMARY}"
echo "PVC:         ${PVC_NAME}"
echo "PV:          ${PV_NAME}"</pre>

<div class="markdown-heading">
&nbsp;
<h3 class="heading-element">Step 4. Configure the source cluster to retain PVs<a class="anchor-link" id="step-4-configure-the-source-cluster-to-retain-pvs"></a></h3>
</div>
<p>If you want to delete the Crunchy source cluster but keep the persistent volumes, the PV reclaim policy must be set to&nbsp;<code><em>Retain</em></code>. For dynamically provisioned PersistentVolumes, the default reclaim policy is&nbsp;<em><code>Delete</code></em>, which removes the data once there are no more PersistentVolumeClaims associated with the PV.</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl patch pv "${PV_NAME}" 
  -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

kubectl get pv -n $MIGRATION_NS</pre>
<p>Delete the Crunchy cluster and uninstall PGO:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl patch postgrescluster crunchy-source -n $MIGRATION_NS 
  --type=json -p='[{"op":"remove","path":"/metadata/finalizers"}]' 2&gt;/dev/null || true

kubectl delete postgrescluster crunchy-source -n $MIGRATION_NS
helm uninstall pgo -n $MIGRATION_NS</pre>
<p>After the PVC is deleted, the PV enters&nbsp;<code><em>Released</em></code>&nbsp;state. A&nbsp;<em><code>Released</code></em>&nbsp;PV retains its old&nbsp;<code><em>claimRef</em></code>&nbsp;and cannot be claimed by a new PVC until it is cleared:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl patch pv "${PV_NAME}" --type=json 
  -p='[{"op":"remove","path":"/spec/claimRef"}]'

kubectl wait pv "${PV_NAME}" 
  --for=jsonpath='{.status.phase}'=Available 
  --timeout=60s</pre>
<p>Label the PV so the Percona PVC selector binds to it exclusively. This prevents accidental binding to another available volume:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl label pv "${PV_NAME}" percona-pv-migration=migrated</pre>
<p>&nbsp;</p>
<h3 class="heading-element">Step 5. Create the Percona cluster with the retained volume<a class="anchor-link" id="step-5-create-the-percona-cluster-with-the-retained-volume"></a></h3>

<pre class="urvanov-syntax-highlighter-plain-tag">kubectl apply -n $MIGRATION_NS 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-pv/examples/02-percona-migrated-cluster.yaml</pre>
<p>The key section that binds the PVC to the labelled PV:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">instances:
  - name: instance1
    replicas: 1
    dataVolumeClaimSpec:
      selector:
        matchLabels:
          percona-pv-migration: migrated</pre>
<p>The Percona Operator creates a PVC with that selector. The PVC binds to the labelled PV, and PostgreSQL starts on the existing PGDATA directory with no restore needed. pgBackRest uses a local PVC-backed repository (<em><code>repo1.volume</code></em>), so no S3 credentials or external storage are required, but you can use S3 storage as well.</p>
<p>Wait for the cluster to become ready and verify the data is intact:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl wait perconapgcluster/percona-migrated 
  -n $MIGRATION_NS 
  --for=jsonpath='{.status.state}'=ready 
  --timeout=600s

PERCONA_PRIMARY=$(kubectl get pod -n $MIGRATION_NS 
  --selector postgres-operator.crunchydata.com/cluster=percona-migrated,postgres-operator.crunchydata.com/role=primary 
  -o jsonpath='{.items[0].metadata.name}')

kubectl -n $MIGRATION_NS exec "${PERCONA_PRIMARY}" -c database -- 
  psql -t -c "SELECT pg_is_in_recovery();"</pre>
<p>Expected output:&nbsp;<code><em>f</em></code>. The cluster is the primary and accepts writes.</p>
<div class="markdown-heading">
&nbsp;
<h3 class="heading-element">Step 6. Scale up replicas<a class="anchor-link" id="step-6-scale-up-replicas"></a></h3>
</div>
<p>The cluster started with a single replica to reuse the migrated PV. Once the primary is healthy, drop the PVC selector and scale out so the operator can provision fresh replica volumes from the storage class:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl patch perconapgcluster percona-migrated 
  --namespace $MIGRATION_NS 
  --type=json 
  -p='[
    {"op":"remove","path":"/spec/instances/0/dataVolumeClaimSpec/selector"},
    {"op":"replace","path":"/spec/instances/0/replicas","value":3}
  ]'

kubectl wait perconapgcluster/percona-migrated 
  --namespace $MIGRATION_NS 
  --for=jsonpath='{.status.state}'=ready 
  --timeout=300s</pre>
<p>Removing the selector here is important: leaving it in place would cause the new replica PVCs to fail provisioning because no other PV carries the migration label.<br>
&nbsp;</p>
<h3 class="heading-element">Step 7. Take a post-migration backup<a class="anchor-link" id="step-7-take-a-post-migration-backup"></a></h3>

<pre class="urvanov-syntax-highlighter-plain-tag">kubectl apply -n $MIGRATION_NS 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-pv/examples/03-post-migration-backup.yaml

kubectl wait perconapgbackup/post-migration-backup 
  -n $MIGRATION_NS 
  --for=jsonpath='{.status.state}'=Succeeded 
  --timeout=600s</pre>
<p>This creates the first backup on the Percona cluster&rsquo;s local pgBackRest repository, establishing a baseline for future PITR restores.</p>
<div class="markdown-heading">
&nbsp;
<h3 class="heading-element">Step 8. Reconnect your application<a class="anchor-link" id="step-8-reconnect-your-application"></a></h3>

<pre class="urvanov-syntax-highlighter-plain-tag">kubectl get service -n $MIGRATION_NS 
  --selector postgres-operator.crunchydata.com/cluster=percona-migrated,postgres-operator.crunchydata.com/role=pgbouncer</pre>

<div class="markdown-heading">
&nbsp;
<h3 class="heading-element">Step 9. Cleanup<a class="anchor-link" id="step-9-cleanup"></a></h3>
</div>
<p>After the migration is verified, remove the migration label from the PV (Step 6 already removed the PVC selector that depended on it):</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl label pv "${PV_NAME}" percona-pv-migration-</pre>
<p>&nbsp;</p>
<div class="markdown-heading">
<h3 class="heading-element">Rollback<a class="anchor-link" id="rollback"></a></h3>
</div>
<p>PV migration is the least rollback-friendly of the three methods. Once the Percona cluster has started writing to the PGDATA directory, the original Crunchy timeline is gone. If you need a way back, take a Crunchy-side pgBackRest backup before Step 4 and treat that backup as your rollback point. Recovery is then a fresh Crunchy cluster restored from that backup.</p>
<div class="markdown-heading">
&nbsp;
<h3 class="heading-element">Troubleshooting<a class="anchor-link" id="troubleshooting"></a></h3>
</div>
<p><strong>PVC stays in&nbsp;<code>Pending</code>&nbsp;state.</strong>&nbsp;The PVC selector did not match the labelled PV. Verify the label and PV phase:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl get pv "${PV_NAME}" --show-labels
kubectl get pv "${PV_NAME}" -o jsonpath='{.status.phase}'</pre>
<p><strong>PostgreSQL fails to start (data directory errors).</strong>&nbsp;Check the database container logs:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl -n $MIGRATION_NS logs "${PERCONA_PRIMARY}" -c database</pre>
<p>If the Crunchy cluster was shut down uncleanly, there may be incomplete WAL. Patroni will attempt crash recovery automatically; check the logs for progress.</p>
<p><strong>PV was deleted before setting&nbsp;<code>Retain</code>.</strong>&nbsp;If the PV was deleted along with the PVC (default&nbsp;<code>Delete</code>&nbsp;policy), the data is gone and PV migration is no longer possible. Use the backup-and-restore migration above, restoring from the most recent pgBackRest backup.</p>
<p>&nbsp;</p>
<div class="markdown-heading">
<h3 class="heading-element">Conclusion<a class="anchor-link" id="conclusion"></a></h3>
</div>
<p>Two more migration paths from the Crunchy Data PostgreSQL Operator to the fully open-source Percona PostgreSQL Operator. Combined with <a href="https://www.percona.com/blog/migrate-from-crunchy-data-to-percona-postgresql-operator-standby-cluster" rel="nofollow">Part 2</a>, the series gives you three production-tested options:</p>
<ul>
<li><strong>Standby cluster</strong>&nbsp;(part 2): near-zero downtime via streaming replication and pgBackRest standby</li>
<li><strong>Backup and restore</strong>&nbsp;(this post): the simplest path, restoring directly from a Crunchy pgBackRest backup</li>
<li><strong>Persistent volume reuse</strong>&nbsp;(this post): when you want to keep storage and skip the data copy</li>
</ul>
<p>All three approaches are safe, predictable, and reversible, with the rollback caveats noted in each section. Because Percona&rsquo;s operator, images, and tooling are 100 percent open source, you keep full control: you can always migrate back to the Crunchy operator, or out to another open-source operator (Zalando, StackGres, CloudNativePG) using the same patterns. That last journey is a topic for a future post.</p>
<p>This post covers basic deployment patterns and simplified configuration examples. If your environment uses custom images, Crunchy enterprise features, or otherwise needs tailored migration steps, contact the Percona team and we will help you plan and execute the move.</p>
<p>&nbsp;</p>
<div class="markdown-heading">
<h3 class="heading-element">Try It Out<a class="anchor-link" id="try-it-out"></a></h3>
</div>
<ul>
<li><strong>Percona Operator for PostgreSQL docs</strong>: <a href="https://docs.percona.com/percona-operator-for-postgresql/latest/">https://docs.percona.com/percona-operator-for-postgresql/latest/</a></li>
<li><strong>GitHub</strong>:&nbsp;<a href="https://github.com/percona/percona-postgresql-operator">https://github.com/percona/percona-postgresql-operator</a></li>
<li><strong>Public roadmap</strong>:&nbsp;<a href="https://github.com/orgs/percona/projects/10/views/6">https://github.com/orgs/percona/projects/10/views/6</a></li>
<li><strong>Community Forum</strong>:&nbsp;<a href="https://forums.percona.com/c/postgresql/percona-kubernetes-operator-for-postgresql/68" rel="nofollow">https://forums.percona.com/c/postgresql/percona-kubernetes-operator-for-postgresql/68</a></li>
</ul>
</div>
</div>
</div>
<p>The post <a href="https://www.percona.com/blog/migrate-from-crunchy-data-to-percona-postgresql-operator-backup-restore-pv-reuse/">Migrate from Crunchy Data PostgreSQL Operator to Percona PostgreSQL Operator: Backup-Restore and PV Reuse</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/migrate-from-crunchy-data-to-percona-postgresql-operator-backup-restore-pv-reuse/">Migrate from Crunchy Data PostgreSQL Operator to Percona PostgreSQL Operator: Backup-Restore and PV Reuse</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Virtuozzo Renews Sponsorship of MariaDB Foundation</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/virtuozzo-renews-sponsorship-of-mariadb-foundation/" />
      <id>https://mariadb.org/virtuozzo-renews-sponsorship-of-mariadb-foundation/</id>
      <updated>2026-05-27T10:48:37+00:00</updated>
      <author><name>Anna Widenius</name></author>
      <summary type="html"><![CDATA[<p>We are delighted to announce that Virtuozzo has renewed its sponsorship of MariaDB Foundation.<br />
Virtuozzo has been a long-standing supporter of open infrastructure, service providers, and cloud platforms, and we are very pleased to continue strengthening our collaboration. …<br />
Continue reading \"Virtuozzo Renews Sponsorship of MariaDB Foundation\"<br />
The post Virtuozzo Renews Sponsorship of MariaDB Foundation appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/virtuozzo-renews-sponsorship-of-mariadb-foundation/">Virtuozzo Renews Sponsorship of MariaDB Foundation</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>We are delighted to announce that <a href="https://www.virtuozzo.com/">Virtuozzo</a> has renewed its sponsorship of MariaDB Foundation.<br>
Virtuozzo has been a long-standing supporter of open infrastructure, service providers, and cloud platforms, and we are very pleased to continue strengthening our collaboration. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/virtuozzo-renews-sponsorship-of-mariadb-foundation/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Virtuozzo Renews Sponsorship of MariaDB Foundation&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/virtuozzo-renews-sponsorship-of-mariadb-foundation/">Virtuozzo Renews Sponsorship of MariaDB Foundation</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/virtuozzo-renews-sponsorship-of-mariadb-foundation/">Virtuozzo Renews Sponsorship of MariaDB Foundation</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>How MariaDB Cloud Optimizes Database Resilience and Cost: A Deep Dive into High Availability</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/how-mariadb-cloud-optimizes-database-resilience-and-cost-a-deep-dive-into-high-availability/" />
      <id>https://mariadb.com/resources/blog/how-mariadb-cloud-optimizes-database-resilience-and-cost-a-deep-dive-into-high-availability/</id>
      <updated>2026-05-26T17:49:30+00:00</updated>
      <author><name>Jags Ramnarayan</name></author>
      <summary type="html"><![CDATA[<p>MariaDB has long been the backbone of mission-critical applications, valued for its rich feature set, developer-friendly SQL dialect and a […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/how-mariadb-cloud-optimizes-database-resilience-and-cost-a-deep-dive-into-high-availability/">How MariaDB Cloud Optimizes Database Resilience and Cost: A Deep Dive into High Availability</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB has long been the backbone of mission-critical applications, valued for its rich feature set, developer-friendly SQL dialect and a massive global ecosystem of tools. But as workloads migrate to the cloud, the conversation has shifted from simple database features to operational excellence. Today, the priority is on seamless scaling, rock-solid security, effortless replication&hellip;</p>
<p><a href="https://mariadb.com/resources/blog/how-mariadb-cloud-optimizes-database-resilience-and-cost-a-deep-dive-into-high-availability/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/how-mariadb-cloud-optimizes-database-resilience-and-cost-a-deep-dive-into-high-availability/">How MariaDB Cloud Optimizes Database Resilience and Cost: A Deep Dive into High Availability</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>How Procurement Leaders Realize ROI from Open Source Databases</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/how-procurement-leaders-realize-roi-from-open-source-databases/" />
      <id>https://www.percona.com/blog/how-procurement-leaders-realize-roi-from-open-source-databases/</id>
      <updated>2026-05-26T15:20:50+00:00</updated>
      <author><name>Scott LaFortune</name></author>
      <summary type="html"><![CDATA[<p>Database purchases are often considered just another IT expense. The primary concerns are limited to license fees and sign support contracts. But this mindset ignores hidden costs like downtime, excess capacity, rising renewal fees, and data transfer charges. The financial sector particularly suffers, as proprietary databases hinder system updates for compliance and real-time AI, impose … Continued<br />
The post How Procurement Leaders Realize ROI from Open Source Databases appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/how-procurement-leaders-realize-roi-from-open-source-databases/">How Procurement Leaders Realize ROI from Open Source Databases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Database purchases are often considered just another IT expense. The primary concerns are limited to license fees and sign support contracts. But this mindset ignores hidden costs like downtime, excess capacity, rising renewal fees, and data transfer charges.</p>
<p>The financial sector particularly suffers, as proprietary databases hinder system updates for compliance and real-time AI, impose rigid pricing, and shift operational risk to the buyer.</p>
<p>Procurement leaders are starting to see this problem. Over <a href="https://www.percona.com/blog/alternatives-to-cloud-dbaas-what-to-look-for/">74% of Database as a Service (DBaaS)</a> users cite high and unpredictable costs as their top challenge due to proprietary pricing structures. Meanwhile, the open source database market is projected to reach <a href="https://market.us/report/open-source-database-market/">$63.48 billion by 2034</a>, signaling a major industry shift.</p>
<p>Switching to open source databases offers procurement teams better financial control, allowing spending to be measured and predicted like any other asset.</p>
<p>This article provides a framework for procurement leaders to realize the ROI of open source databases. It explains how to move beyond license-focused sourcing to a strategy that prioritizes risk reduction, spend predictability, and vendor optionality.</p>
<h2>The procurement blind spot: What database TCO really includes<a class="anchor-link" id="the-procurement-blind-spot-what-database-tco-really-includes"></a></h2>
<p>License fees often become the total cost baseline in many sourcing cycles. But that license cost is only a fraction of the true database Total Cost of Ownership (TCO). The massive operational and strategic costs are hidden beneath the surface. The cost drivers show up in six areas:</p>
<ul>
<li><strong>Outage and SLA penalties:</strong> Downtime incurred due to vendor-managed recovery or architecture constraints.</li>
<li><strong>Forced over-provisioning:</strong> Licensing models that require institutions to buy capacity they may not fully use (because licenses are sold in &ldquo;blocks&rdquo; or &ldquo;cores&rdquo;). If your workload requires 9 cores, you are often forced to pay for 16.</li>
<li><strong>Escalating renewal pricing:</strong> Per-core or per-instance fees that climb with infrastructure growth, unrelated to feature value.</li>
<li><strong>Data egress fees and platform taxes:</strong> Cloud DBaaS charges for cross-region replication, data exports, backups, and traffic that accumulate unpredictably.</li>
<li><strong>Staffing and operational overhead:</strong> Database administrators and Site Reliability Engineers (SREs) dedicating time to tuning, patching, and managing vendor-specific tooling.</li>
<li><strong>Migration and switching costs:</strong> The financial and technical burden of moving data if vendor changes or licensing terms shift.</li>
</ul>
<h3>Downtime as a financial liability (Not a technical issue)<a class="anchor-link" id="downtime-as-a-financial-liability-not-a-technical-issue"></a></h3>
<p>Procurement teams may not always be the primary owners of downtime risk, but they often influence it through vendor selection, contract terms, and support coverage. Because outages carry measurable business impact, support responsiveness and recovery capability should be evaluated as a financial exposure.</p>
<p>For example, critical-incident response and restoration expectations must be defined and aligned with the organization&rsquo;s risk tolerance. If not, the institution may be accepting avoidable financial and operational exposure during high-severity events.</p>
<p>The financial model is simple:</p>
<p><strong>(Incident Frequency) &times; (Incident Duration) &times; (Cost Per Hour) = Annual Risk Exposure</strong></p>
<p>For a bank with three major outages per year, averaging 4 hours each, with a $500K/hour impact:</p>
<p><strong>3 &times; 4 &times; $500,000 = $6 million in annual downtime risk</strong></p>
<p>Open source works best when supported by vendor-agnostic experts like <a href="https://www.percona.com/services/support/policies">Percona&rsquo;s</a>. It allows procurement to source support that focuses on restoring service across the entire stack, rather than defending a specific piece of software.</p>
<h3>Cost predictability vs. vendor-driven cost escalation<a class="anchor-link" id="cost-predictability-vs-vendor-driven-cost-escalation"></a></h3>
<p>Budget forecasting becomes impossible when database costs are unpredictable. Yet proprietary licensing introduces multiple mechanisms that undermine forecast accuracy and negatively affect business success.</p>
<ul>
<li><strong>The scaling penalty:</strong> As your customer base grows and you add more hardware, your software costs increase exponentially because of per-core or per-socket licensing.</li>
<li><strong>Tier creep:</strong> You might start on a Standard tier, but as soon as you need a critical security feature like advanced encryption or granular auditing for DORA <a href="https://www.eiopa.europa.eu/digital-operational-resilience-act-dora_en">(Digital Operational Resilience Act)</a> compliance, you are forced into an Enterprise tier that can cost more.</li>
</ul>
<p>In contrast, open source separates the software cost from growth. If you double your infrastructure to handle peak trading volumes, your software cost remains zero. It lets procurement provide the business with a linear, predictable cost model (you only pay for the infrastructure you use and the expertise required to run it).</p>
<h3>Vendor lock-in and contract leverage<a class="anchor-link" id="vendor-lock-in-and-contract-leverage"></a></h3>
<p>The primary objective of a sales team from a proprietary vendor is to make customers more committed to their products. The more vendor-specific features you use, the harder it is for procurement to negotiate when renewing the contract. Over time:</p>
<ul>
<li><strong>Switching costs add up:</strong> Data migration, changing schemas, and application changes create high barriers to leaving.</li>
<li><strong>Vendor leverage grows:</strong> As integration deepens, alternatives become more costly, reducing competition in renewal negotiations.</li>
<li><strong>Renewal pricing rises:</strong> With fewer alternatives, vendors increase renewal fees, confident that institutions cannot easily leave.</li>
</ul>
<p><a href="https://www.percona.com/blog/redis-users-want-a-change/">Percona&rsquo;s research on Redis</a> users shows that nearly 75% have considered or tested alternatives when licensing terms change, but most couldn&rsquo;t really switch. This is vendor lock-in at its most destructive, as institutions resent the vendor but cannot leave.</p>
<p>Open source gives institutions more options (restores vendor optionality). Procurement can regain power through:</p>
<ul>
<li><strong>Multi-vendor support:</strong> If one support provider underperforms or raises prices, you can move your support contract to another provider without migrating your data.</li>
<li><strong>Deployment flexibility:</strong> Open source can run on-premise, in any cloud (AWS, Azure, GCP), or in a hybrid model.</li>
<li><strong>Lower switching costs:</strong> Since open source uses standard protocols, it is easier to find talent and tools that work across the stack, and reduce the exit cost of any single relationship.</li>
</ul>
<h3>Operational efficiency as a budget control mechanism<a class="anchor-link" id="operational-efficiency-as-a-budget-control-mechanism"></a></h3>
<p>Database operations often increase operating expenses. When teams react to problems rather than prevent them, labor costs rise without notice. Inefficient database management raises labor costs in two ways:</p>
<ul>
<li><strong>Specialized labor scarcity:</strong> Finding a specialist for a proprietary database is expensive.</li>
<li><strong>Reactive engineering:</strong> When database performance is poor, teams spend more time fixing issues instead of building new products.</li>
</ul>
<p>Switching to an open-source system with integrated management tools, like <a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management">Percona Monitoring and Management</a>, can help the organization save valuable engineering time.</p>
<p>For example, if a 10-person engineering team spends 20% of their time on manual database maintenance, that&rsquo;s like paying two full-time employees just to keep things running. Improving tools and support reduces this work and provides immediate operational ROI.</p>
<h3>Data egress fees: The hidden variable cost<a class="anchor-link" id="data-egress-fees-the-hidden-variable-cost"></a></h3>
<p>In cloud services, it&rsquo;s usually free to get your data in, but costs can skyrocket when you want to get your data out. Many managed proprietary DBaaS platforms are designed to trap your data. They make it easy to scale up, but charge massive data egress fees if you want to move that data to a third-party analytics tool or a different cloud provider.</p>
<p>Open source databases, particularly when run on <a href="https://www.percona.com/blog/managed-database-vs-kubernetes-taking-back-control-of-your-cloud-costs-and-agility/">Kubernetes</a> or self-managed infrastructure, give you full control over the data path. With that control, procurement and platform stakeholders can design data flows that reduce unnecessary cross-cloud transfers and help minimize egress fees.</p>
<h3>Annualized ROIs Summary for procurement<a class="anchor-link" id="annualized-rois-summary-for-procurement"></a></h3>
<p>When presenting the move to open source to the executive team, procurement should frame the benefits across the following financial pillars:</p>
<table style="border-collapse: collapse;width: 100%">
<thead>
<tr>
<th style="border: 1px solid #ccc;padding: 10px 12px;text-align: left;background-color: #f4f4f4">ROI Lever</th>
<th style="border: 1px solid #ccc;padding: 10px 12px;text-align: left;background-color: #f4f4f4">Procurement outcome</th>
<th style="border: 1px solid #ccc;padding: 10px 12px;text-align: left;background-color: #f4f4f4">Financial impact</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #ccc;padding: 10px 12px"><strong>Risk avoidance</strong></td>
<td style="border: 1px solid #ccc;padding: 10px 12px">Reduced downtime frequency and duration.</td>
<td style="border: 1px solid #ccc;padding: 10px 12px">Lowered black swan event liability.</td>
</tr>
<tr>
<td style="border: 1px solid #ccc;padding: 10px 12px"><strong>Spend control</strong></td>
<td style="border: 1px solid #ccc;padding: 10px 12px">Removal of license multipliers.</td>
<td style="border: 1px solid #ccc;padding: 10px 12px">Predictable, linear cost growth.</td>
</tr>
<tr>
<td style="border: 1px solid #ccc;padding: 10px 12px"><strong>Leverage</strong></td>
<td style="border: 1px solid #ccc;padding: 10px 12px">Multi-vendor support options.</td>
<td style="border: 1px solid #ccc;padding: 10px 12px">Stronger renewal negotiating power.</td>
</tr>
<tr>
<td style="border: 1px solid #ccc;padding: 10px 12px"><strong>Productivity</strong></td>
<td style="border: 1px solid #ccc;padding: 10px 12px">Reduced manual DB management.</td>
<td style="border: 1px solid #ccc;padding: 10px 12px">Reclaiming expensive engineering hours.</td>
</tr>
</tbody>
</table>
<h2>
Why open source aligns with procurement objectives<a class="anchor-link" id="why-open-source-aligns-with-procurement-objectives"></a></h2>
<p>Modern procurement is about governance, compliance, and strategic alignment. Open source databases align with these goals better than proprietary ones:</p>
<ul>
<li><strong>No licensing premiums for scale or performance.</strong> A 10x increase in data does not mean 10x higher license fees.</li>
<li><strong>Transparent, auditable cost structures.</strong> Procurement knows exactly what they are paying for.</li>
<li><strong>Support can be competitively sourced.</strong> Institutions are not locked into a single software vendor.</li>
<li><strong>Better compliance and security.</strong> Open code enables internal security reviews, transparency for auditors, and helps meet regulatory requirements.</li>
</ul>
<h2>Operating open source with procurement-grade assurance<a class="anchor-link" id="operating-open-source-with-procurement-grade-assurance"></a></h2>
<p>A common objection to open source is that it&rsquo;s unsupported. Procurement teams need operational assurance, confidence that open source environments meet the same regulatory, availability, and financial standards as proprietary systems.</p>
<p>When evaluating a support partner, procurement should require:</p>
<ul>
<li><strong>SLA clarity:</strong> Specific, contractually backed response and resolution times.</li>
<li><strong>Multi-database coverage:</strong> One contract that covers MySQL, PostgreSQL, and MongoDB to reduce contract sprawl.</li>
<li><strong>Regulated-environment experience:</strong> A partner who understands PCI-DSS, SOC2, and the high-compliance needs of finance.</li>
</ul>
<p>Percona meets all of these criteria and offers procurement with a partner that turns technical operations into financial metrics.</p>
<h2>Where Percona fits<a class="anchor-link" id="where-percona-fits"></a></h2>
<p>Percona operationalizes open source databases for regulated, mission-critical environments. It delivers measurable outcomes across four strategic dimensions for procurement teams:</p>
<h3>Independent, vendor-neutral support model<a class="anchor-link" id="independent-vendor-neutral-support-model"></a></h3>
<p>Percona provides <a href="https://www.percona.com/services/support">technology-agnostic support</a> across MySQL, PostgreSQL, MongoDB, MariaDB, and Valkey. It operates independently of cloud providers and database vendors, supporting on premises, cloud, and hybrid environments. This vendor neutrality ensures institutions maintain full control over technology choices without being locked into specific platforms or ecosystems.</p>
<h3>Predictable support costs without licensing dependency<a class="anchor-link" id="predictable-support-costs-without-licensing-dependency"></a></h3>
<p><a href="https://www.percona.com/blog/why-companies-are-saying-no-to-proprietary-software-including-mongodb/">Percona&rsquo;s pricing model</a> decouples support costs from database licensing and creates transparent, forecastable expenses. While proprietary databases force organizations to pay escalating per-core or usage-based fees, Percona&rsquo;s support subscriptions operate independently of infrastructure growth. For example, organizations like <a href="https://experience.percona.com/case-study/bbva/">BBVA</a> reduced licensing and support costs while simultaneously improving backup performance by 20% after migrating to Percona Server for MongoDB.</p>
<h3>Proven experience supporting regulated financial systems<a class="anchor-link" id="proven-experience-supporting-regulated-financial-systems"></a></h3>
<p>Percona supports regulated financial systems, including Fortune 500 companies and government agencies, and meets <a href="https://experience.percona.com/postgresql/postgre-sql-security-solution-brief/">compliance standards</a> such as HIPAA, PCI DSS, GDPR, and DORA EU.</p>
<p>Major financial services implementations include:</p>
<ul>
<li><strong><a href="https://www.percona.com/about-percona/case-studies/merchant-warrior-counts-on-percona-for-critical-availability">Merchant Warrior</a>:</strong> Australia&rsquo;s payments gateway relies on Percona for critical MySQL availability, supporting millions of transactions across 30,000+ customers.</li>
<li><strong><a href="https://www.percona.com/about-percona/newsroom/press-releases/multipay-group-chooses-percona-database-management">MultiPay</a> and <a href="https://www.percona.com/about-percona/case-studies/bukalapak">Bukalapak</a>:</strong> Financial services and e-commerce platforms leveraging Percona&rsquo;s support to maintain high availability and optimize deployment performance.</li>
</ul>
<h2>Conclusion: Database performance as a spend control strategy<a class="anchor-link" id="conclusion-database-performance-as-a-spend-control-strategy"></a></h2>
<p>Databases have evolved from technical infrastructure into financial assets. Their uptime, performance, and flexibility influence costs, vendor leverage, and operational resilience. For procurement, buying databases is a strategic investment to control expenses and manage risks.</p>
<p>Organizations gain predictable costs, measurable ROI, vendor optionality, and long-term operational control by choosing open-source databases and partnering with Percona. These advantages compound over time, while proprietary systems often fall short.</p>
<p>Get started with <a href="https://www.percona.com/software/percona-operators">Percona Operators</a> and see how consistency, scale, and freedom come together.</p>
<p>The post <a href="https://www.percona.com/blog/how-procurement-leaders-realize-roi-from-open-source-databases/">How Procurement Leaders Realize ROI from Open Source Databases</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/how-procurement-leaders-realize-roi-from-open-source-databases/">How Procurement Leaders Realize ROI from Open Source Databases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>ProxySQL joins MariaDB Foundation as Silver Sponsor</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/proxysql-joins-mariadb-foundation/" />
      <id>https://mariadb.org/proxysql-joins-mariadb-foundation/</id>
      <updated>2026-05-26T06:11:40+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>We are very pleased to welcome ProxySQL as a Silver Sponsor of the MariaDB Foundation.<br />
ProxySQL is the leading proxy for MySQL and has recently focused on supporting more and more of MariaDB, both with the Proxy and with other open-source projects ProxySQL is stewarding, like dbdeployer and orchestrator. …<br />
Continue reading \"ProxySQL joins MariaDB Foundation as Silver Sponsor\"<br />
The post ProxySQL joins MariaDB Foundation as Silver Sponsor appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/proxysql-joins-mariadb-foundation/">ProxySQL joins MariaDB Foundation as Silver Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>We are very pleased to welcome <a href="https://proxysql.com/">ProxySQL</a> as a Silver Sponsor of the MariaDB Foundation.<br>
ProxySQL is the leading proxy for MySQL and has recently focused on supporting more and more of MariaDB, both with the Proxy and with other open-source projects ProxySQL is stewarding, like <a href="http://github.com/ProxySQL/dbdeployer">dbdeployer</a> and <a href="https://github.com/ProxySQL/orchestrator">orchestrator</a>. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/proxysql-joins-mariadb-foundation/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;ProxySQL joins MariaDB Foundation as Silver Sponsor&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/proxysql-joins-mariadb-foundation/">ProxySQL joins MariaDB Foundation as Silver Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/proxysql-joins-mariadb-foundation/">ProxySQL joins MariaDB Foundation as Silver Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>ProxySQL joins MariaDB Foundation as Silver Sponsor</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/psql-joins-mariadb-foundation/" />
      <id>https://mariadb.org/psql-joins-mariadb-foundation/</id>
      <updated>2026-05-26T06:10:00+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>We are very pleased to welcome ProxySQL as a Silver Sponsor of the MariaDB Foundation.<br />
ProxySQL is the leading proxy for MySQL and has recently focused on supporting more and more of MariaDB, both with the Proxy and with other open-source projects ProxySQL is stewarding, like dbdeployer and orchestrator. …<br />
Continue reading \"ProxySQL joins MariaDB Foundation as Silver Sponsor\"<br />
The post ProxySQL joins MariaDB Foundation as Silver Sponsor appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/psql-joins-mariadb-foundation/">ProxySQL joins MariaDB Foundation as Silver Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>We are very pleased to welcome <a href="https://proxysql.com/">ProxySQL</a> as a Silver Sponsor of the MariaDB Foundation.<br>
ProxySQL is the leading proxy for MySQL and has recently focused on supporting more and more of MariaDB, both with the Proxy and with other open-source projects ProxySQL is stewarding, like <a href="http://github.com/ProxySQL/dbdeployer">dbdeployer</a> and <a href="https://github.com/ProxySQL/orchestrator">orchestrator</a>. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/psql-joins-mariadb-foundation/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;ProxySQL joins MariaDB Foundation as Silver Sponsor&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/psql-joins-mariadb-foundation/">ProxySQL joins MariaDB Foundation as Silver Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/psql-joins-mariadb-foundation/">ProxySQL joins MariaDB Foundation as Silver Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>ProxySQL joins MariaDB Foundation as Silver Sponsor</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/new-sponsor-proxysql-mariadb-foundation/" />
      <id>https://mariadb.org/new-sponsor-proxysql-mariadb-foundation/</id>
      <updated>2026-05-26T06:10:00+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>We are very pleased to welcome ProxySQL as a Silver Sponsor of the MariaDB Foundation.<br />
ProxySQL is the leading proxy for MySQL and has recently focused on supporting more and more of MariaDB, both with the Proxy and with other open-source projects ProxySQL is stewarding, like dbdeployer and orchestrator. …<br />
Continue reading \"ProxySQL joins MariaDB Foundation as Silver Sponsor\"<br />
The post ProxySQL joins MariaDB Foundation as Silver Sponsor appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/new-sponsor-proxysql-mariadb-foundation/">ProxySQL joins MariaDB Foundation as Silver Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>We are very pleased to welcome <a href="https://proxysql.com/">ProxySQL</a> as a Silver Sponsor of the MariaDB Foundation.<br>
ProxySQL is the leading proxy for MySQL and has recently focused on supporting more and more of MariaDB, both with the Proxy and with other open-source projects ProxySQL is stewarding, like <a href="http://github.com/ProxySQL/dbdeployer">dbdeployer</a> and <a href="https://github.com/ProxySQL/orchestrator">orchestrator</a>. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/new-sponsor-proxysql-mariadb-foundation/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;ProxySQL joins MariaDB Foundation as Silver Sponsor&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/new-sponsor-proxysql-mariadb-foundation/">ProxySQL joins MariaDB Foundation as Silver Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/new-sponsor-proxysql-mariadb-foundation/">ProxySQL joins MariaDB Foundation as Silver Sponsor</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Drupal recommends MariaDB</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/drupal-recommends-mariadb/" />
      <id>https://mariadb.org/drupal-recommends-mariadb/</id>
      <updated>2026-05-25T11:29:10+00:00</updated>
      <author><name>Robert Silén</name></author>
      <summary type="html"><![CDATA[<p>MariaDB is now clearly listed as the recommended database in Drupal’s official documentation. Following community discussion on Drupal.org, the Database server requirements now lists MariaDB first, and identifies it as the recommended database for Drupal 10, 11, and 12. …<br />
Continue reading \"Drupal recommends MariaDB\"<br />
The post Drupal recommends MariaDB appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/drupal-recommends-mariadb/">Drupal recommends MariaDB</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB is now clearly listed as the recommended database in Drupal&rsquo;s official documentation. Following <a href="https://www.drupal.org/project/drupal/issues/3478097">community discussion on </a><a href="http://drupal.org">Drupal.org</a>, the <a href="https://www.drupal.org/docs/getting-started/system-requirements/database-server-requirements">Database server requirements</a> now lists MariaDB first, and identifies it as the recommended database for Drupal 10, 11, and 12. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/drupal-recommends-mariadb/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Drupal recommends MariaDB&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/drupal-recommends-mariadb/">Drupal recommends MariaDB</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/drupal-recommends-mariadb/">Drupal recommends MariaDB</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Running TidesDB as a MySQL 9.7 storage engine</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/running-tidesdb-as-a-mysql-9-7-storage-engine/" />
      <id>https://www.percona.com/blog/running-tidesdb-as-a-mysql-9-7-storage-engine/</id>
      <updated>2026-05-25T10:51:55+00:00</updated>
      <author><name>Evgeniy Patlan</name></author>
      <summary type="html"><![CDATA[<p>tidesdb-mysql is an experimental build that was developed to verify how TidesDB, the LSM-tree key/value engine, can work with MySQL 9.7 as a storage engine. The current build is v0.2.4, and it’s an experiment, not a finished product. So you can use it in your tests if you also want to try TidesDB with MySQL … Continued<br />
The post Running TidesDB as a MySQL 9.7 storage engine appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/running-tidesdb-as-a-mysql-9-7-storage-engine/">Running TidesDB as a MySQL 9.7 storage engine</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">tidesdb-mysql is an experimental build that was developed to verify how TidesDB, the LSM-tree key/value engine, can work with MySQL 9.7 as a storage engine. The current build is v0.2.4, </span><span style="font-weight: 400">and it&rsquo;s an experiment, not a finished product. So you can use it in your tests if you also want to try TidesDB with MySQL and compare with MariaDB</span></p>
<h2><b>Why we made it</b><a class="anchor-link" id="why-we-made-it"></a></h2>
<p><span style="font-weight: 400">There was already a way to use TidesDB from SQL. It&rsquo;s TideSQL, which loads the engine into MariaDB as </span><span style="font-weight: 400">ha_tidesdb</span><span style="font-weight: 400">, and it works fine. But it doesn&rsquo;t work with MySQL. So we wanted TidesDB to work with</span><span style="font-weight: 400"> MySQL 9.7.</span></p>
<p><span style="font-weight: 400">MariaDB and MySQL share a lot of history, but they are not the same. We couldn&rsquo;t just recompile the MariaDB plugin against MySQL headers and call it done. The one thing that stayed put through all of it was TidesDB itself, doing exactly what it does anywhere else. Only the server wrapped around was changed. In result we got our implementation, so if you&rsquo;re on MySQL, you no longer have to switch to MariaDB to give TidesDB a try.</span></p>
<h2><b>What it actually is</b><a class="anchor-link" id="what-it-actually-is"></a></h2>
<p><span style="font-weight: 400">tidesdb-mysql</span><span style="font-weight: 400"> is a loadable plugin, </span><span style="font-weight: 400">ha_tidesdb.so</span><span style="font-weight: 400">. The engine gets built on its own and loaded into the server at runtime, the same shape as the MariaDB version. It speaks the MySQL handler API and wires MySQL tables and indexes onto TidesDB column families. After it loads, </span><span style="font-weight: 400">TidesDB</span><span style="font-weight: 400"> sits right next to </span><span style="font-weight: 400">InnoDB</span><span style="font-weight: 400"> in </span><span style="font-weight: 400">SHOW ENGINES</span><span style="font-weight: 400"> and you choose it per table.</span></p>
<h2><b>Getting started</b><a class="anchor-link" id="getting-started"></a></h2>
<p><span style="font-weight: 400">All you need is Docker. Pull the image and start it:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">docker pull perconalab/tidesdb-mysql:0.2.4

docker run -d --name tidesdb 
-e MYSQL_ROOT_PASSWORD=secret 
-p 3306:3306 
perconalab/tidesdb-mysql:0.2.4</pre>
<p><span style="font-weight: 400">The plugin is baked into this image and loaded on boot, so there&rsquo;s no </span><span style="font-weight: 400">INSTALL PLUGIN</span><span style="font-weight: 400"> step to remember. Confirm the engine is live:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">docker exec tidesdb mysql -uroot -psecret 
-e "SELECT engine, support FROM information_schema.engines WHERE engine='TidesDB';"
# TidesDB | YES</pre>
<p>&nbsp;</p>
<p><span style="font-weight: 400">Now make a table and treat it like any other:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">CREATE DATABASE shop;
USE shop;

CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
price DECIMAL(10,2) NOT NULL,
KEY idx_price (price)
) ENGINE=TIDESDB;

INSERT INTO products (name, price) VALUES ('Widget', 9.99), ('Gadget', 24.50);
SELECT * FROM products WHERE price &lt; 20;</pre>
<p>&nbsp;</p>
<p><span style="font-weight: 400">Transactions, secondary indexes, the usual SQL, it all behaves:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">START TRANSACTION;
UPDATE products SET price = price + 1 WHERE name = 'Widget';
COMMIT;</pre>
<p>&nbsp;</p>
<p><span style="font-weight: 400">Per-table TidesDB options ride along in MySQL&rsquo;s </span><span style="font-weight: 400">ENGINE_ATTRIBUTE</span><span style="font-weight: 400"> JSON field. MySQL doesn&rsquo;t have MariaDB&rsquo;s </span><span style="font-weight: 400">COMPRESSION=&hellip;</span><span style="font-weight: 400"> grammar, so the options are identical but you write them differently:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE events (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
msg TEXT
) ENGINE=TIDESDB
ENGINE_ATTRIBUTE='{"compression":"ZSTD","bloom_filter":true}';</pre>
<p><span style="font-weight: 400">Compression accepts </span><span style="font-weight: 400">NONE</span><span style="font-weight: 400">, </span><span style="font-weight: 400">SNAPPY</span><span style="font-weight: 400">, </span><span style="font-weight: 400">LZ4</span><span style="font-weight: 400">, </span><span style="font-weight: 400">ZSTD</span><span style="font-weight: 400">, or </span><span style="font-weight: 400">LZ4_FAST</span><span style="font-weight: 400">. Server-wide knobs live in system variables such as </span><span style="font-weight: 400">tidesdb_default_compression</span><span style="font-weight: 400">, </span><span style="font-weight: 400">tidesdb_block_cache_size</span><span style="font-weight: 400">, </span><span style="font-weight: 400">tidesdb_compaction_threads</span><span style="font-weight: 400">, and </span><span style="font-weight: 400">tidesdb_flush_threads</span><span style="font-weight: 400">. The full list is in </span><span style="font-weight: 400">docs/build-and-load.md</span><span style="font-weight: 400">.</span></p>
<h2><b>Prove the crash recovery</b><a class="anchor-link" id="prove-the-crash-recovery"></a></h2>
<p><span style="font-weight: 400">Write a handful of rows, kill the server with no clean shutdown, bring it back, and count what&rsquo;s left:</span></p>
<p><i><span style="font-weight: 400"># 1. Write rows inside a transaction and COMMIT.</span></i><i><span style="font-weight: 400"><br>
</span></i></p>
<pre class="urvanov-syntax-highlighter-plain-tag">docker exec -i tidesdb mysql -uroot -psecret &lt;&lt;'SQL'
CREATE DATABASE IF NOT EXISTS t;
CREATE TABLE IF NOT EXISTS t.kv (k INT PRIMARY KEY, v VARCHAR(32)) ENGINE=TIDESDB;
BEGIN;
INSERT INTO t.kv VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');
COMMIT;
SELECT COUNT(*) AS before_crash FROM t.kv; -- 5
SQL</pre>
<p><span style="font-weight: 400"><br>
</span> <i><span style="font-weight: 400"># 2. Hard-kill the server (no graceful shutdown) and restart it.</span></i><i><span style="font-weight: 400"><br>
</span></i><b></b></p>
<pre class="urvanov-syntax-highlighter-plain-tag">docker kill -s KILL tidesdb
docker start tidesdb
until docker exec tidesdb mysql -uroot -psecret -e 'SELECT 1' &gt;/dev/null 2&gt;&amp;1; do sleep 2; done</pre>
<p><b></b><i><span style="font-weight: 400"># 3. The committed rows are still there.</span></i><i><span style="font-weight: 400"><br>
</span></i></p>
<pre class="urvanov-syntax-highlighter-plain-tag">docker exec tidesdb mysql -uroot -psecret 
-e "SELECT COUNT(*) AS after_crash FROM t.kv;" -- 5</pre>
<p><span style="font-weight: 400">after_crash</span><span style="font-weight: 400"> should come back equal to </span><span style="font-weight: 400">before_crash</span><span style="font-weight: 400">.</span></p>
<h2><b>A few more things to try</b><a class="anchor-link" id="a-few-more-things-to-try"></a></h2>
<p><span style="font-weight: 400">Compression is the one people ask about first, so here&rsquo;s a table that leans on it. We generate a couple thousand rows of repetitive text, which is exactly the shape ZSTD likes:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE logs (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
level VARCHAR(8) NOT NULL,
body TEXT,
KEY idx_level (level)
) ENGINE=TIDESDB
ENGINE_ATTRIBUTE='{"compression":"ZSTD","bloom_filter":true}';</pre>

<pre class="urvanov-syntax-highlighter-plain-tag">INSERT INTO logs (level, body)
SELECT IF(RAND() &lt; 0.2, 'warn', 'info'),
REPEAT('the quick brown fox jumps over the lazy dog ', 40)
FROM information_schema.columns
LIMIT 2000;</pre>

<pre class="urvanov-syntax-highlighter-plain-tag">SELECT level, COUNT(*) AS rows FROM logs GROUP BY level;
SELECT id, LEFT(body, 30) AS preview FROM logs WHERE id = 1000;</pre>
<p><span style="font-weight: 400">The rows go in compressed and come back out as the original text, so queries don&rsquo;t change at all. If you want to confirm the option actually landed on the table rather than being silently dropped, ask the server what it stored:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">SHOW CREATE TABLE logsG
-- ENGINE=TIDESDB ... ENGINE_ATTRIBUTE='{"compression":"ZSTD","bloom_filter":true}'</pre>
<p><span style="font-weight: 400">The bloom filter from that same attribute is what keeps point lookups cheap once the data has compacted down into several on-disk files:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">SELECT id, level FROM logs WHERE id = 1500;</pre>
<p><span style="font-weight: 400">A JSON column behaves the way you&rsquo;d expect, including the </span><span style="font-weight: 400">-&gt;&gt;</span><span style="font-weight: 400"> extraction operator:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE kv (k VARCHAR(64) PRIMARY KEY, v JSON) ENGINE=TIDESDB;

INSERT INTO kv VALUES
('en', JSON_OBJECT('lang','English', 'msg','hello')),
('es', JSON_OBJECT('lang','Spanish', 'msg','hola')),
('fr', JSON_OBJECT('lang','French', 'msg','bonjour'));

SELECT k, v-&gt;&gt;'$.lang' AS language, v-&gt;&gt;'$.msg' AS greeting
FROM kv
ORDER BY k;</pre>
<p><span style="font-weight: 400">And the secondary index on </span><span style="font-weight: 400">products</span><span style="font-weight: 400"> from earlier is a real index, not decoration. A range query uses it, and </span><span style="font-weight: 400">EXPLAIN</span><span style="font-weight: 400"> will show </span><span style="font-weight: 400">idx_price</span><span style="font-weight: 400"> in the </span><span style="font-weight: 400">key</span><span style="font-weight: 400"> column:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">SELECT name, price FROM products WHERE price BETWEEN 5 AND 20 ORDER BY price;

EXPLAIN SELECT name, price FROM products WHERE price BETWEEN 5 AND 20;</pre>

<h2><b>What works, and what doesn&rsquo;t yet</b><a class="anchor-link" id="what-works-and-what-doesnt-yet"></a></h2>
<p><span style="font-weight: 400">Quite a bit works. The common column types are all there, primary keys single and composite, </span><span style="font-weight: 400">AUTO_INCREMENT</span><span style="font-weight: 400">, secondary indexes with index-condition pushdown, </span><span style="font-weight: 400">COMMIT</span><span style="font-weight: 400">/</span><span style="font-weight: 400">ROLLBACK</span><span style="font-weight: 400">, </span><span style="font-weight: 400">REPLACE</span><span style="font-weight: 400"> and </span><span style="font-weight: 400">INSERT &hellip; ON DUPLICATE KEY UPDATE</span><span style="font-weight: 400">, online add/drop index, instant add column, full-text search, spatial indexes, per-row TTL, per-table compression and bloom filters, at-rest encryption, and mixed-engine transactions where a TidesDB table and an InnoDB table share one </span><span style="font-weight: 400">BEGIN &hellip; COMMIT</span><span style="font-weight: 400">. The functional test suite, which we lifted from TideSQL and then extended, passes 58 of 58 executed tests.</span></p>
<p><span style="font-weight: 400">A few things you should know about before you lean on it:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Native </span> <span style="font-weight: 400">partitioning and the MySQL 9 vector column type aren&rsquo;t implemented. </span> <span style="font-weight: 400">Those two test cases are skipped deliberately.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Atomic, </span> <span style="font-weight: 400">crash-safe DDL (the data-dictionary integration) is wired up but we </span> <span style="font-weight: 400">haven&rsquo;t driven it end-to-end yet. Your data writes are crash-safe; </span> <span style="font-weight: 400">schema changes during a crash are next on the list.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Replication, </span> <span style="font-weight: 400">foreign keys, and nested savepoints aren&rsquo;t in scope at the moment.</span></li>
</ul>
<p><span style="font-weight: 400">Treat v0.2.5 as a serious experiment. It&rsquo;s solid enough that committed data rides through a crash, and it&rsquo;s not something we&rsquo;d point production traffic at yet.</span></p>
<h2><b>Try it, then tell us</b><a class="anchor-link" id="try-it-then-tell-us"></a></h2>

<pre class="urvanov-syntax-highlighter-plain-tag">docker pull perconalab/tidesdb-mysql:0.2.5</pre>
<p><span style="font-weight: 400">That&rsquo;s the whole setup. Spin up a table with </span><span style="font-weight: 400">ENGINE=TIDESDB</span><span style="font-weight: 400">, run the crash demo, and point your own SQL at it. The source, the build scripts, and the engine patches all live in the</span><a href="https://github.com/EvgeniyPatlan/tidesdb-mysql"> <span style="font-weight: 400">tidesdb-mysql</span></a><span style="font-weight: 400"> repository, and the durability fixes are written up in </span><span style="font-weight: 400">KNOWN-ISSUES.md</span><span style="font-weight: 400">. This is a tool made by users for users, so if you give it a spin, we&rsquo;d genuinely like to hear what held up and what fell over.</span></p>
<p>The post <a href="https://www.percona.com/blog/running-tidesdb-as-a-mysql-9-7-storage-engine/">Running TidesDB as a MySQL 9.7 storage engine</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/running-tidesdb-as-a-mysql-9-7-storage-engine/">Running TidesDB as a MySQL 9.7 storage engine</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Migrate from Crunchy Data PostgreSQL Operator to Percona PostgreSQL Operator: Standby Cluster Method</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/migrate-from-crunchy-data-to-percona-postgresql-operator-standby-cluster/" />
      <id>https://www.percona.com/blog/migrate-from-crunchy-data-to-percona-postgresql-operator-standby-cluster/</id>
      <updated>2026-05-25T07:49:01+00:00</updated>
      <author><name>Slava Sarzhan</name></author>
      <summary type="html"><![CDATA[<p>A Crunchy to Percona PostgreSQL migration is more straightforward than most cross-operator moves on Kubernetes, because the Percona PostgreSQL Operator is a hard fork of the Crunchy Data PostgreSQL Operator. Same Patroni HA, same pgBackRest backups, same overall CRD shape. This post walks through the safest of the three migration paths: a standby cluster method … Continued<br />
The post Migrate from Crunchy Data PostgreSQL Operator to Percona PostgreSQL Operator: Standby Cluster Method appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/migrate-from-crunchy-data-to-percona-postgresql-operator-standby-cluster/">Migrate from Crunchy Data PostgreSQL Operator to Percona PostgreSQL Operator: Standby Cluster Method</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><img decoding="async" loading="lazy" class="aligncenter wp-image-47080 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/hero-1024x375.png" alt="" width="1024" height="375"></p>
<p>A Crunchy to Percona PostgreSQL migration is more straightforward than most cross-operator moves on Kubernetes, because the Percona PostgreSQL Operator is a hard fork of the Crunchy Data PostgreSQL Operator. Same Patroni HA, same pgBackRest backups, same overall CRD shape. This post walks through the safest of the three migration paths: a standby cluster method with near-zero downtime.</p>
<p><span style="font-weight: 400">This is </span><b><strong>part 2</strong> </b><span style="font-weight: 400">of a </span><b><strong>3-part </strong></b><span style="font-weight: 400">series on running PostgreSQL on Kubernetes with a fully open-source operator. </span><a href="https://www.percona.com/blog/not-all-open-source-is-equal-choosing-postgresql-operator-kubernetes-2026/"><span style="font-weight: 400">Part 1</span></a><span style="font-weight: 400"> walked through the changing open-source landscape and announced the hard fork of the Crunchy Data PostgreSQL Operator into the fully independent <a href="https://github.com/percona/percona-postgresql-operator/tree/v3.0.0"><strong>Percona PostgreSQL Operator v3.0.0</strong></a>.</span></p>
<p><span style="font-weight: 400">This post is the first practical playbook of the series. It covers the </span><b><strong>standby cluster method</strong>, </b><span style="font-weight: 400">the safest migration path when the downtime budget is tight. <strong>Part 3</strong> will cover two simpler paths: backup-and-restore and persistent-volume reuse.</span></p>
<p><span style="font-weight: 400">If you are landing here without context on why you might want to migrate at all, </span><a href="https://www.percona.com/blog/not-all-open-source-is-equal-choosing-postgresql-operator-kubernetes-2026/"><span style="font-weight: 400">start with part 1</span></a><span style="font-weight: 400">. The rest of this post assumes you have already decided to move and want a tested playbook.</span></p>
<p>&nbsp;</p>
<h2><b>Migration approach in one paragraph</b><a class="anchor-link" id="migration-approach-in-one-paragraph"></a></h2>
<p><span style="font-weight: 400">The <strong>Percona PostgreSQL Kubernetes Operator</strong> is a hard fork of the Crunchy Data PostgreSQL Kubernetes Operator, which simplifies the migration paths considerably: the same underlying tools (<strong>Patroni</strong>, <strong>pgBackRest</strong>, <strong>PgBouncer</strong>) and the same overall design are used in both operators. All three migration paths in this series are reversible: because Percona&rsquo;s operator is fully open source and remains compatible with the same backup format, the move back to Crunchy is also possible if your team decides to walk it</span></p>
<p>&nbsp;</p>
<h3><b>A note on the storage layer</b><a class="anchor-link" id="a-note-on-the-storage-layer"></a></h3>
<p><span style="font-weight: 400">All examples in this guide use an in-cluster <a href="https://github.com/seaweedfs/seaweedfs">SeaweedFS</a> instance as the <strong>pgBackRest</strong> S3 repository. <strong>SeaweedFS</strong> is Apache-2.0 licensed, actively maintained, and a clean drop-in replacement for the role <strong>MinIO</strong> used to fill in this stack. Any other S3-compatible storage works just as well: AWS S3, Google Cloud Storage (via HMAC keys), Ceph RadosGW, Cloudflare R2, and so on. For non-<strong>SeaweedFS</strong> endpoints, remove <em>repo1-s3-uri-style: path</em>&nbsp;and <em>repo1-s3-verify-tls: &ldquo;n&rdquo;</em> from the pgBackRest configuration and replace the endpoint with your provider&rsquo;s URL.</span></p>
<p>&nbsp;</p>
<h3><b>What this series does NOT cover</b><a class="anchor-link" id="what-this-series-does-not-cover"></a></h3>
<p><span style="font-weight: 400">To keep scope honest:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Application-side connection-string changes beyond updating to the new pgBouncer service. If your app uses connection-pool tuning, custom auth, or a service mesh, that work stays with you.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Schema-changing upgrades, major PostgreSQL version upgrades, or extension migrations. The PostgreSQL major version must match between the source and the target.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Crunchy enterprise-only features like TDE, Crunchy Postgres for Kubernetes-specific operators, or pgBackRest custom encryption. If your environment uses these, contact the Percona team for a tailored plan.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Operating two operators against the same namespace before the PGO hard fork. Use Percona PostgreSQL Operator v3.0.0 or higher.</span></li>
</ul>
<p>&nbsp;</p>
<h3><b>Tested with</b><a class="anchor-link" id="tested-with"></a></h3>
<table>
<tbody>
<tr>
<td><b>Component</b></td>
<td><b>Version</b></td>
</tr>
<tr>
<td><span style="font-weight: 400">Crunchy Data PostgreSQL Kubernetes Operator</span></td>
<td><span style="font-weight: 400">v5.8.x (tested on v5.8.7)</span></td>
</tr>
<tr>
<td><a href="https://docs.percona.com/percona-operator-for-postgresql/latest/"><span style="font-weight: 400">Percona PostgreSQL Kubernetes Operator</span></a></td>
<td>v3.x.x (tested on v3.0.0)</td>
</tr>
<tr>
<td><span style="font-weight: 400">PostgreSQL</span></td>
<td><span style="font-weight: 400">18 (must match between source and target)</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Object storage</span></td>
<td><span style="font-weight: 400">SeaweedFS (Apache-2.0), or any other S3-compatible service accessible from all cluster pods</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Tools</span></td>
<td><span style="font-weight: 400">kubectl</span><span style="font-weight: 400">, </span><span style="font-weight: 400">helm</span> <span style="font-weight: 400">(v3)</span><span style="font-weight: 400">, </span><span style="font-weight: 400">yq</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">Different versions may differ slightly in CR fields or behavior. Always consult the official documentation for the operator and PostgreSQL version you are running.</span></p>
<p>&nbsp;</p>
<h2><b>Migration using a standby cluster</b><a class="anchor-link" id="migration-using-a-standby-cluster"></a></h2>
<p><span style="font-weight: 400">This is the safest method when the downtime budget is tight. The Percona cluster is brought up as a standby of the Crunchy primary, catches up via pgBackRest plus streaming replication, and is promoted at cutover. The only downtime is the cutover step itself.</span></p>
<p><span style="font-weight: 400">You can wire the standby in two ways, and combining both gives you maximum safety:</span></p>
<ul>
<li style="font-weight: 400"><b>pgBackRest repo-based standby</b><span style="font-weight: 400"> seeds the standby from the latest base backup and replays archived WAL</span></li>
<li style="font-weight: 400"><b>Streaming replication </b><span style="font-weight: 400">keeps the standby in sync with the live primary</span></li>
</ul>
<p>&nbsp;</p>
<h3><b>Overview</b><a class="anchor-link" id="overview"></a></h3>
<p><span style="font-weight: 400"><br>
</span><img decoding="async" loading="lazy" class="aligncenter wp-image-47083 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/standby-1024x393.png" alt="" width="1024" height="393"></p>
<p>&nbsp;</p>
<h3><b>Before you begin</b><a class="anchor-link" id="before-you-begin"></a></h3>
<p><span style="font-weight: 400">Set the target namespace once. Every command in this guide reads from this variable, so you can change it in a single place:<br>
</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">export MIGRATION_NS=postgres-migration
kubectl create namespace $MIGRATION_NS</pre>
<p>&nbsp;</p>
<h3><b>Deploy SeaweedFS</b><a class="anchor-link" id="deploy-seaweedfs"></a></h3>
<p><span style="font-weight: 400">Skip this step if you already have an S3-compatible repository (AWS S3, GCS, Ceph). Update the endpoint and credentials in the YAML examples accordingly.</span></p>
<p><span style="font-weight: 400"><strong>SeaweedFS</strong> provides an S3-compatible object store that runs inside Kubernetes. Both operators will use it as the shared pgBackRest WAL archive.</span></p>
<p><b>TLS is required. </b><span style="font-weight: 400"><strong>pgBackRest</strong> always connects to S3 endpoints over HTTPS, even when </span><em><span style="font-weight: 400">repo1-s3-verify-tls: &ldquo;n&rdquo;</span></em><span style="font-weight: 400"> is set (that flag skips certificate verification, it does not fall back to HTTP). The steps below generate a self-signed certificate and pass it to <strong>SeaweedFS</strong> via <strong>Helm</strong> values.<br>
</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag"># Generate a self-signed TLS certificate for SeaweedFS S3
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 
  -keyout /tmp/seaweedfs.key 
  -out /tmp/seaweedfs.crt 
  -subj "/CN=seaweedfs-all-in-one"

kubectl -n $MIGRATION_NS create secret tls seaweedfs-s3-tls 
  --cert=/tmp/seaweedfs.crt 
  --key=/tmp/seaweedfs.key

helm repo add seaweedfs https://seaweedfs.github.io/seaweedfs/helm
helm repo update

helm install seaweedfs seaweedfs/seaweedfs 
  --namespace $MIGRATION_NS 
  --version 4.23.0 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-standby/examples/seaweedfs-values.yaml 
  --wait</pre>
<p><span style="font-weight: 400">The Helm values file in the repo creates the </span><em><span style="font-weight: 400">pg-migration</span></em><span style="font-weight: 400"> bucket on first start, so no separate </span><em><span style="font-weight: 400">aws s3 mb</span></em><span style="font-weight: 400"> step is needed.</span></p>
<p>&nbsp;</p>
<h3><b>Step 0. Create pgBackRest secrets</b><a class="anchor-link" id="step-0-create-pgbackrest-secrets"></a></h3>
<p><span style="font-weight: 400">Both operators need credentials to read and write the shared <strong>SeaweedFS</strong> bucket. Apply the secrets from <a href="https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-standby/examples/01-pgbackrest-secret.yaml">examples/01-pgbackrest-secret.yaml</a> after filling in your access key and secret key:<br>
</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag"># Copy and edit the file first to set your credentials.

kubectl apply -n $MIGRATION_NS 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-standby/examples/01-pgbackrest-secret.yaml</pre>
<p><span style="font-weight: 400">Both secrets contain the same <strong>SeaweedFS</strong> credentials (</span><em><span style="font-weight: 400">pgmigration</span></em><span style="font-weight: 400"> / </span><em><span style="font-weight: 400">pgmigration123</span></em><span style="font-weight: 400"><em>)</em>. For AWS S3, replace those with your IAM access key ID and secret access key.</span></p>
<p>&nbsp;</p>
<h3><b>Step 1. Start with your existing Crunchy Data cluster</b><a class="anchor-link" id="step-1-start-with-your-existing-crunchy-data-cluster"></a></h3>
<p><span style="font-weight: 400">If you already have a running Crunchy cluster, ensure its pgBackRest </span><em><span style="font-weight: 400">repo1</span></em><span style="font-weight: 400"> points at the shared bucket and path. The </span><em><span style="font-weight: 400">repo1-path</span></em><span style="font-weight: 400"> value must be identical in both cluster specs. Mismatched paths will prevent the Percona standby from finding the WAL archive.</span></p>
<p><strong>The Helm install below is shown only as a quick way to reproduce this blog post&rsquo;s example. The migration steps in the rest of this post do not depend on how you deployed the source operator.</strong></p>
<p><span style="font-weight: 400">Optional: deploy a Crunchy operator to test the migration end to end:<br>
</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">helm install pgo 
  oci://registry.developers.crunchydata.com/crunchydata/pgo 
  -n $MIGRATION_NS 
  --version 5.8.7 
  --set singleNamespace=true 
  --wait</pre>
<p><span style="font-weight: 400"><br>
Apply </span><a href="https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-standby/examples/02-crunchy-source-cluster.yaml"><span style="font-weight: 400">examples/02-crunchy-source-cluster.yaml</span></a><span style="font-weight: 400"> (or adapt your existing cluster&rsquo;s pgBackRest config):</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl apply -n $MIGRATION_NS 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-standby/examples/02-crunchy-source-cluster.yaml</pre>
<p><span style="font-weight: 400"><br>
The key pgBackRest settings in the example:<br>
</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">global:
  repo1-path: /crunchy-to-percona/repo1   # shared path, must match Percona side
  repo1-s3-uri-style: path                # required for path-style S3 endpoints (SeaweedFS, MinIO)
  repo1-s3-verify-tls: "n"                # skip TLS verification for self-signed cert; remove for AWS S3
repos:
  - name: repo1
    s3:
      bucket: pg-migration
      endpoint: seaweedfs-all-in-one.postgres-migration.svc.cluster.local:8443
      region: us-east-1</pre>
<p><span style="font-weight: 400"><br>
<span style="font-weight: 400">Wait for the cluster to be ready:</span></span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl wait pod 
  --selector postgres-operator.crunchydata.com/cluster=crunchy-source,postgres-operator.crunchydata.com/data=postgres 
  --namespace $MIGRATION_NS 
  --for=condition=Ready 
  --timeout=300s</pre>
<p>&nbsp;</p>
<h3><b><br>
Step 2. Trigger a full backup on the Crunchy cluster</b><a class="anchor-link" id="step-2-trigger-a-full-backup-on-the-crunchy-cluster"></a></h3>
<p><span style="font-weight: 400">Wait for the pgBackRest stanza to be created:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl wait postgrescluster/crunchy-source 
  -n $MIGRATION_NS 
  --for=jsonpath='{.status.pgbackrest.repos[0].stanzaCreated}'=true 
  --timeout=300s</pre>
<p><span style="font-weight: 400">Take a full backup before creating the Percona standby. This gives the standby a recent base to restore from, so it only needs to replay a small amount of WAL to catch up. This matches the realistic production migration pattern.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl annotate postgrescluster crunchy-source 
  --namespace $MIGRATION_NS 
  postgres-operator.crunchydata.com/pgbackrest-backup="$(date +%s)"</pre>
<p><span style="font-weight: 400"><br>
Wait for the backup job to complete:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl wait job 
  -l postgres-operator.crunchydata.com/pgbackrest-backup=manual,postgres-operator.crunchydata.com/cluster=crunchy-source 
  -n $MIGRATION_NS 
  --for=condition=Complete 
  --timeout=600s</pre>
<p>&nbsp;</p>
<h3><b><br>
Step 3. Copy TLS certificates (cross-namespace only)</b><a class="anchor-link" id="step-3-copy-tls-certificates-cross-namespace-only"></a></h3>
<p><span style="font-weight: 400">If the Percona cluster is in a different namespace from the Crunchy cluster, copy the Crunchy TLS secrets to the Percona namespace. These allow mutual TLS authentication during streaming replication:<br>
</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">for secret in crunchy-source-cluster-cert crunchy-source-replication-cert; do
  kubectl get secret "${secret}" -n &lt;CRUNCHY_NS&gt; -o json | 
    yq '{"apiVersion": .apiVersion, "kind": .kind, "data": .data,
         "metadata": {"name": .metadata.name}, "type": .type}' -o yaml | 
    kubectl -n $MIGRATION_NS apply -f -
done</pre>
<p><span style="font-weight: 400">If both clusters are in the same namespace, skip this step. The secrets are already accessible.</span></p>
<p>&nbsp;</p>
<h3><b>Step 4. Deploy the Percona PG Operator</b><a class="anchor-link" id="step-4-deploy-the-percona-pg-operator"></a></h3>
<p><span style="font-weight: 400">The Crunchy PGO operator can stay in the same or a different namespace.<br>
</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl apply -n $MIGRATION_NS --server-side 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/tags/v3.0.0/deploy/bundle.yaml</pre>
<p><span style="font-weight: 400">Wait until the operator deployment is ready:<br>
</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl wait deployment percona-postgresql-operator 
  -n $MIGRATION_NS 
  --for=condition=Available 
  --timeout=120s</pre>
<p>&nbsp;</p>
<h3><b>Step 5. Create the Percona cluster in standby mode</b><a class="anchor-link" id="step-5-create-the-percona-cluster-in-standby-mode"></a></h3>
<p><strong>Note</strong>: The&nbsp;<code>kubectl apply</code> below pulls the CR manifest from the <code>migration-from-crunchy-guide</code>&nbsp;branch of the operator repo, which is the source for this guide&rsquo;s examples. For production deployments, follow&nbsp;<a href="https://docs.percona.com/percona-operator-for-postgresql/latest/kubectl.html" rel="nofollow">the official Percona Operator for PostgreSQL installation documentation</a>&nbsp;and pin to a released version tag rather than a feature branch.</p>
<p><span style="font-weight: 400">Apply </span><a href="https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-standby/examples/03-percona-standby-cluster.yaml"><span style="font-weight: 400">examples/03-percona-standby-cluster.yaml</span></a><span style="font-weight: 400">:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl apply -n $MIGRATION_NS 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-standby/examples/03-percona-standby-cluster.yaml</pre>
<p><span style="font-weight: 400">The key settings that wire the Percona cluster to the Crunchy source:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">standby:
  enabled: true
  repoName: repo1                             # restore initial base backup from this repo
  host: crunchy-source-ha.postgres-migration.svc.cluster.local
  port: 5432

secrets:
  customTLSSecret:
    name: crunchy-source-cluster-cert         # Crunchy CA for mutual TLS
  customReplicationTLSSecret:
    name: crunchy-source-replication-cert     # cert for _crunchyreplication user</pre>
<p><span style="font-weight: 400">The Percona operator will:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Restore the base backup from the <strong>SeaweedFS</strong> bucket.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Replay WAL from <strong>SeaweedFS</strong> until it catches up with the live Crunchy cluster.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Switch to streaming replication from </span><em><span style="font-weight: 400">crunchy-source-ha</span><span style="font-weight: 400">.</span></em></li>
</ol>
<p><span style="font-weight: 400">Wait for the cluster to reach the ready state:<br>
</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl wait perconapgcluster/percona-standby 
  -n $MIGRATION_NS 
  --for=jsonpath='{.status.state}'=ready 
  --timeout=600s</pre>
<p><span style="font-weight: 400">Verify that data is replicating to the standby:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">STANDBY_POD=$(kubectl get pod -n $MIGRATION_NS 
  -l postgres-operator.crunchydata.com/cluster=percona-standby,postgres-operator.crunchydata.com/data=postgres 
  -o jsonpath='{.items[0].metadata.name}')

kubectl -n $MIGRATION_NS exec "${STANDBY_POD}" -c database -- 
  psql -t -c "SELECT pg_is_in_recovery(), pg_last_wal_replay_lsn();"</pre>
<p><span style="font-weight: 400">Expected output: </span><span style="font-weight: 400">t</span><span style="font-weight: 400"> (in recovery) and a non-null LSN.</span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400"><br>
</span></p>
<p>&nbsp;</p>
<h3><b>Step 6. Verify replication lag before cutover</b><a class="anchor-link" id="step-6-verify-replication-lag-before-cutover"></a></h3>
<p><span style="font-weight: 400">Query the Crunchy primary to confirm the Percona standby has caught up:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">CRUNCHY_PRIMARY=$(kubectl get pod 
  -l postgres-operator.crunchydata.com/cluster=crunchy-source,postgres-operator.crunchydata.com/role=master 
  -n $MIGRATION_NS 
  -o jsonpath='{.items[0].metadata.name}')

kubectl -n $MIGRATION_NS exec "${CRUNCHY_PRIMARY}" -c database -- 
  psql -c "
    SELECT
        client_addr,
        state,
        pg_wal_lsn_diff(sent_lsn, replay_lsn) AS byte_lag,
        write_lag,
        flush_lag,
        replay_lag
    FROM pg_stat_replication;
  "</pre>
<p><span style="font-weight: 400">Proceed to the next step only when </span><em><span style="font-weight: 400">write_lag</span></em><span style="font-weight: 400"> and </span><em><span style="font-weight: 400">replay_lag</span></em><span style="font-weight: 400"> are <em>NULL</em> or under a few seconds.</span></p>
<p>&nbsp;</p>
<h3><b>Step 7. Cutover the Crunchy cluster</b><a class="anchor-link" id="step-7-cutover-the-crunchy-cluster"></a></h3>
<p><span style="font-weight: 400">This is the only step that causes downtime. Stop accepting writes on the application side, then patch the Crunchy cluster into standby mode. Patroni steps down and archives the final WAL.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl patch postgrescluster crunchy-source 
  -n $MIGRATION_NS 
  --type=merge 
  -p '{"spec": {"standby": {"enabled": true, "repoName": "repo1"}}}'</pre>
<p><span style="font-weight: 400">Verify demotion (poll until <em>pg_is_in_recovery()</em> returns <em>t</em>):</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl -n $MIGRATION_NS exec "${CRUNCHY_PRIMARY}" -c database -- 
  psql -t -c "SELECT pg_is_in_recovery();"</pre>
<p>&nbsp;</p>
<h3><b>Step 8. (Optional) Shut down the Crunchy cluster</b><a class="anchor-link" id="step-8-optional-shut-down-the-crunchy-cluster"></a></h3>
<p><span style="font-weight: 400">Once the Percona standby has replayed all WAL, shut down the Crunchy cluster to prevent split-brain:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl patch postgrescluster crunchy-source 
  -n $MIGRATION_NS 
  --type=merge 
  -p '{"spec": {"shutdown": true}}'

kubectl wait pod 
  -l postgres-operator.crunchydata.com/cluster=crunchy-source,postgres-operator.crunchydata.com/data=postgres 
  -n $MIGRATION_NS 
  --for=delete 
  --timeout=120s || true</pre>
<p>&nbsp;</p>
<h3><b>Step 9. Promote the Percona cluster</b><a class="anchor-link" id="step-9-promote-the-percona-cluster"></a></h3>
<p><span style="font-weight: 400">Confirm that the Percona standby has finished replaying all WAL (the LSN stops advancing):</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl -n $MIGRATION_NS exec "${STANDBY_POD}" -c database -- 
  psql -t -c "SELECT pg_last_wal_replay_lsn();"</pre>
<p><span style="font-weight: 400">Run this a few times. When the LSN is stable, replay is complete.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl patch perconapgcluster percona-standby 
  -n $MIGRATION_NS 
  --type=merge 
  -p '{"spec": {"standby": {"enabled": false}}}'</pre>
<p><span style="font-weight: 400">Wait for the cluster to become ready and confirm it is writable:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl wait perconapgcluster/percona-standby 
  -n $MIGRATION_NS 
  --for=jsonpath='{.status.state}'=ready 
  --timeout=480s

PERCONA_PRIMARY=$(kubectl get pod -n $MIGRATION_NS 
  -l postgres-operator.crunchydata.com/cluster=percona-standby,postgres-operator.crunchydata.com/role=primary 
  -o jsonpath='{.items[0].metadata.name}')

kubectl -n $MIGRATION_NS exec "${PERCONA_PRIMARY}" -c database -- 
  psql -t -c "SELECT pg_is_in_recovery();"</pre>
<p><span style="font-weight: 400">Expected output: </span><em><span style="font-weight: 400">f</span></em><span style="font-weight: 400"> (the cluster is now the primary and accepts writes).</span></p>
<p>&nbsp;</p>
<h3><b>Step 10. Verify stanza creation</b><a class="anchor-link" id="step-10-verify-stanza-creation"></a></h3>

<pre class="urvanov-syntax-highlighter-plain-tag">kubectl wait perconapgcluster/percona-standby 
  -n $MIGRATION_NS 
  --for=jsonpath='{.status.pgbackrest.repos[0].stanzaCreated}'=true 
  --timeout=300s</pre>
<p>&nbsp;</p>
<h3><b>Step 11. Take a post-migration backup</b><a class="anchor-link" id="step-11-take-a-post-migration-backup"></a></h3>
<p><span style="font-weight: 400">Apply </span><a href="https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-standby/examples/04-post-migration-backup.yaml"><span style="font-weight: 400">examples/04-post-migration-backup.yaml</span></a><span style="font-weight: 400">:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl apply -n $MIGRATION_NS 
  -f https://raw.githubusercontent.com/percona/percona-postgresql-operator/refs/heads/migration-from-crunchy-guide/e2e-tests/tests/migration-from-crunchy-standby/examples/04-post-migration-backup.yaml

kubectl wait perconapgbackup/post-migration-backup 
  -n $MIGRATION_NS 
  --for=jsonpath='{.status.state}'=Succeeded 
  --timeout=600s</pre>
<p><span style="font-weight: 400">This creates a clean recovery point on the new timeline. All future PITR restores will use this backup as their starting point, independent of the old Crunchy WAL archive.</span></p>
<p>&nbsp;</p>
<h3><b>Reconnecting your application</b><a class="anchor-link" id="reconnecting-your-application"></a></h3>
<p><span style="font-weight: 400">Update your application&rsquo;s connection string to point at the Percona cluster&rsquo;s pgBouncer service:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl get service -n $MIGRATION_NS 
  -l postgres-operator.crunchydata.com/cluster=percona-standby,postgres-operator.crunchydata.com/role=pgbouncer</pre>
<p><span style="font-weight: 400">This migration path works almost entirely out of the box. For users coming from the Crunchy Data PostgreSQL Operator, this method feels familiar because it leverages the same standby/replica mechanisms used for HA and disaster recovery. The key difference is that you can now use this familiar mechanism to migrate safely to the Percona PostgreSQL Operator, a fully open-source alternative running on a fully open-source storage layer.</span></p>
<p>&nbsp;</p>
<h3><b>Rollback</b><a class="anchor-link" id="rollback"></a></h3>
<p><span style="font-weight: 400">The standby method is the most rollback-friendly of the three. Until you take the post-migration backup, the Crunchy cluster still holds the original timeline. To roll back:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Stop writes on the Percona side and patch the Percona cluster back into standby mode (</span><em><span style="font-weight: 400">spec.standby.enabled: true</span></em><span style="font-weight: 400">).</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Patch the Crunchy cluster out of standby mode and let Patroni promote it.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Verify with </span><em><span style="font-weight: 400">pg_is_in_recovery()</span></em><span style="font-weight: 400"> on both sides.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Switch the application connection string back to the Crunchy pgBouncer service.</span></li>
</ol>
<p><span style="font-weight: 400">After Step 11 (post-migration backup), the timelines have diverged. From that point, the rollback story is the same as a fresh restore, and you should treat the Crunchy cluster as a historical reference, not a live target.</span></p>
<p>&nbsp;</p>
<h3><b>Troubleshooting</b><a class="anchor-link" id="troubleshooting"></a></h3>
<p><em><strong>Percona standby not connecting to the Crunchy primary. </strong></em><span style="font-weight: 400">Verify the </span><em><span style="font-weight: 400">crunchy-source-ha</span></em><span style="font-weight: 400"> service resolves from within the Percona pod:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl -n $MIGRATION_NS exec "${STANDBY_POD}" -c database -- 
  bash -c "getent hosts crunchy-source-ha.${MIGRATION_NS}.svc.cluster.local"</pre>
<p><em><b>Replication authentication errors</b></em><span style="font-weight: 400"><em>.</em> The Percona standby authenticates as the </span><span style="font-weight: 400">_crunchyreplication</span><span style="font-weight: 400"> PostgreSQL user using the certificate in </span><span style="font-weight: 400">crunchy-source-replication-cert</span><span style="font-weight: 400">. Verify the secret exists and matches what the Crunchy operator generated:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl get secret crunchy-source-replication-cert -n $MIGRATION_NS</pre>
<p><em><strong>pgBackRest restore fails</strong><b>.</b></em><span style="font-weight: 400"> Confirm both secrets contain identical credentials and that </span><em><span style="font-weight: 400">repo1-path</span></em><span style="font-weight: 400"> is the same in both cluster specs (</span><em><span style="font-weight: 400">/crunchy-to-percona/repo1</span></em><span style="font-weight: 400"> in this guide). Mismatched paths cause an </span><span style="font-weight: 400"><em>archive.info</em> missing</span><span style="font-weight: 400"> error. Verify the bucket is reachable:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl run -i --rm s3-check 
  --image=perconalab/awscli 
  --restart=Never 
  -n $MIGRATION_NS 
  -- bash -c "
    AWS_ACCESS_KEY_ID=pgmigration 
    AWS_SECRET_ACCESS_KEY=pgmigration123 
    AWS_DEFAULT_REGION=us-east-1 
    aws --endpoint-url https://seaweedfs-all-in-one.${MIGRATION_NS}.svc.cluster.local:8443 
        --no-verify-ssl 
        s3 ls s3://pg-migration
  "</pre>
<p><em><strong>Timeline history file (00000002.history) missing after promotion.</strong></em> <span style="font-weight: 400"> <span style="font-weight: 400">This is a known </span><a href="https://github.com/CrunchyData/postgres-operator/issues/4472"><span style="font-weight: 400">issue</span></a><span style="font-weight: 400"> with Crunchy PGO&rsquo;s async archive mode. After promotion, push the history file synchronously:</span></span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">kubectl -n $MIGRATION_NS exec "${PERCONA_PRIMARY}" -c database -- 
  bash -c "
    pgbackrest --stanza=db --no-archive-async 
      archive-push "${PGDATA}/pg_wal/00000002.history" || true
  "</pre>
<p>&nbsp;</p>
<h3><b>What&rsquo;s next</b><a class="anchor-link" id="whats-next"></a></h3>
<p><span style="font-weight: 400">This was the safest migration path. </span><strong>Part 3</strong><span style="font-weight: 400"> will cover two simpler options:</span></p>
<ul>
<li style="font-weight: 400"><b>Backup and restore.</b><span style="font-weight: 400"> The simplest path. You take a Crunchy pgBackRest backup and the Percona cluster bootstraps from it. Cutover is the time between the final backup and pointing the application at the new cluster.</span></li>
<li style="font-weight: 400"><b>Persistent volume reuse.</b><span style="font-weight: 400"> For when you want to skip the data copy entirely. The Percona cluster takes over the existing PGDATA volume, no restore step required.</span></li>
</ul>
<p><span style="font-weight: 400">Pick the method that fits your downtime budget, data size, and storage layout.</span></p>
<p><span style="font-weight: 400">This post covers basic deployment patterns and simplified configuration examples. If your environment is more complex, uses custom images, includes Crunchy enterprise features like TDE, or otherwise needs tailored migration steps, contact the Percona team and we will help you plan and execute the move.</span></p>
<p>&nbsp;</p>
<h3><b>Try It Out</b><a class="anchor-link" id="try-it-out"></a></h3>
<ul>
<li style="font-weight: 400"><b>Percona Operator for PostgreSQL docs</b><span style="font-weight: 400">: <a href="https://docs.percona.com/percona-operator-for-postgresql/latest/">https://docs.percona.com/percona-operator-for-postgresql/latest/</a></span></li>
<li style="font-weight: 400"><b>GitHub</b><span style="font-weight: 400">: </span><a href="https://github.com/percona/percona-postgresql-operator"><span style="font-weight: 400">https://github.com/percona/percona-postgresql-operator</span></a></li>
<li style="font-weight: 400"><b>Public roadmap</b><span style="font-weight: 400">: </span><a href="https://github.com/orgs/percona/projects/10/views/6"><span style="font-weight: 400">https://github.com/orgs/percona/projects/10/views/6</span></a></li>
<li style="font-weight: 400"><b>Issue tracker: </b><a href="https://github.com/percona/percona-postgresql-operator/issues"><span style="font-weight: 400">https://github.com/percona/percona-postgresql-operator/issues</span></a></li>
<li style="font-weight: 400"><b>Community Forum</b>: <a href="https://forums.percona.com/c/postgresql/percona-kubernetes-operator-for-postgresql/68">https://forums.percona.com/c/postgresql/percona-kubernetes-operator-for-postgresql/68</a></li>
</ul>
<p>The post <a href="https://www.percona.com/blog/migrate-from-crunchy-data-to-percona-postgresql-operator-standby-cluster/">Migrate from Crunchy Data PostgreSQL Operator to Percona PostgreSQL Operator: Standby Cluster Method</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/migrate-from-crunchy-data-to-percona-postgresql-operator-standby-cluster/">Migrate from Crunchy Data PostgreSQL Operator to Percona PostgreSQL Operator: Standby Cluster Method</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Write for the Percona Community</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/05/22/write-for-percona-community/" />
      <id>https://percona.community/blog/2026/05/22/write-for-percona-community/</id>
      <updated>2026-05-22T11:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>You’ve fixed something gnarly in production this year. You’ve migrated a database that nobody wanted to touch. You’ve built something on top of Percona Operators, or Percona Toolkit, or Percona Monitoring and Management (PMM), and you’ve learned things along the way that aren’t written down anywhere yet.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/22/write-for-percona-community/">Write for the Percona Community</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>You&rsquo;ve fixed something gnarly in production this year. You&rsquo;ve migrated a database that nobody wanted to touch. You&rsquo;ve built something on top of Percona Operators, or Percona Toolkit, or Percona Monitoring and Management (PMM), and you&rsquo;ve learned things along the way that aren&rsquo;t written down anywhere yet.</p>
<p>Write it up. We&rsquo;ll publish it, and we&rsquo;ll pay you.</p>
<h2 id="what-were-doing">What we&rsquo;re doing<a class="anchor-link" id="what-were-doing"></a></h2>
<p>The Percona Community Writers Program publishes technical posts from the people actually using these tools &mdash; DBAs, developers, contributors, and engineers running real workloads. Posts go up on <a href="https://percona.community/blog" target="_blank" rel="noopener noreferrer">percona.community/blog</a> under your name, with your bio and links.</p>
<p>For every post we publish, you get:</p>
<ul>
<li><strong>$350</strong> paid out after publication</li>
<li><strong>Community engagement points</strong> you can redeem in our swag store for t-shirts, stickers, and other items</li>
</ul>
<p>The points stack across contributions. The more you write, the more you collect.</p>
<h3 id="a-note-on-payment">A note on payment<a class="anchor-link" id="a-note-on-payment"></a></h3>
<p><em>Not everyone can accept payment for writing &mdash; employment contracts, tax situations, visa rules, and conflict-of-interest policies all get in the way. If that&rsquo;s you, we&rsquo;ll donate the same $350 to an open source project or community of your choice on your behalf. Tell us who to send it to when you pitch.</em></p>
<h2 id="what-we-want-to-read">What we want to read<a class="anchor-link" id="what-we-want-to-read"></a></h2>
<p>Anything you&rsquo;ve done with the Percona stack &mdash; or alongside it &mdash; that another engineer would learn from. Some directions to consider:</p>
<ul>
<li><strong>Percona Operators</strong> &mdash; running databases on Kubernetes, scaling decisions, upgrade paths, what surprised you</li>
<li><strong>Percona Toolkit</strong> &mdash; how you use specific tools in your day-to-day, scripts you&rsquo;ve built around them, edge cases</li>
<li><strong>Migrations</strong> &mdash; moving between versions, between database engines, on-premises to cloud, the parts that aren&rsquo;t in the docs</li>
<li><strong>Troubleshooting</strong> &mdash; a real incident, what you saw, what fixed it, what you&rsquo;d do differently</li>
<li><strong>Percona Monitoring and Management (PMM)</strong> &mdash; dashboards you&rsquo;ve built, alerts that actually catch things, integrations</li>
<li><strong>Databases themselves</strong> &mdash; MySQL, PostgreSQL, MongoDB, MariaDB, Valkey, anything in the open source database world you&rsquo;re hands-on with</li>
</ul>
<p>We&rsquo;re not only interested in Percona-product posts. If you&rsquo;re active in the wider open source database community &mdash; contributing to MySQL, PostgreSQL, Valkey, or anywhere else &mdash; we want to hear about that work too. Your projects, your perspective, your hard-won opinions.</p>
<h2 id="standards">Standards<a class="anchor-link" id="standards"></a></h2>
<p>Every submission is reviewed by the community team for technical accuracy and grammar before it goes live. We&rsquo;re not gatekeeping &mdash; we&rsquo;re making sure your name goes on something solid.</p>
<p>One firm rule: <strong>no AI-generated content</strong>. We run every submission through <a href="https://gptzero.me/" target="_blank" rel="noopener noreferrer">GPTZero</a> and it has to come back clean. We&rsquo;re publishing your voice and your experience, not a model&rsquo;s summary of either. If you used AI to help draft, that&rsquo;s fine &mdash; but the post needs to read as yours and pass the check.</p>
<h2 id="how-to-start">How to start<a class="anchor-link" id="how-to-start"></a></h2>
<p>Pitch us first. A couple of sentences on what you want to write about and why you&rsquo;re the person to write it is enough. We&rsquo;ll reply with feedback, a timeline, and any direction that helps you write a stronger post.</p>
<p>You don&rsquo;t need to be a published writer. You need to have done something and be willing to explain how. A 900-word post about how you debugged a replication lag issue last quarter is more valuable than a 3,000-word survey of the database landscape.</p>
<p>Send pitches and questions to the Percona Community team &mdash; by filling in <strong><a href="https://share.hsforms.com/2quoru-zrSli2l-89aiiJggg9e0" target="_blank" rel="noopener noreferrer">this form</a></strong>.</p>
<div class="hs-form-frame" data-region="na1" data-form-id="aaea2bbb-eceb-4a58-b697-ef3d6a288982" data-portal-id="758664"></div>
<h3 id="open-topics-blog-talks-guides">Open topics: blog, talks, guides<a class="anchor-link" id="open-topics-blog-talks-guides"></a></h3>
<p>Not sure where to start? Here are some directions we&rsquo;d love to see covered. Pick one, narrow it down to something you&rsquo;ve actually done, and pitch us.</p>
<p><strong>Databases</strong></p>
<ul>
<li>Automating database setup for production in under a few hours</li>
<li>Backup and disaster recovery strategies that hold up</li>
<li>Failure stories &mdash; what broke, what you learned</li>
</ul>
<p><strong>DevOps and reliability</strong></p>
<ul>
<li>Database Reliability Engineering (DBRE) in practice</li>
<li>Site Reliability Engineering (SRE) applied to databases</li>
<li>Monitoring and SLAs that mean something</li>
<li>Useful scripts you actually run in production</li>
<li>Testing and QA for database changes</li>
</ul>
<p><strong>Distributed computing</strong></p>
<ul>
<li>Consensus algorithms and real-world implementations</li>
<li>Synchronous vs asynchronous replication &mdash; trade-offs and where each fits</li>
</ul>
<p><strong>How-tos</strong></p>
<ul>
<li>Moving from a single node to a cluster (any DB engine)</li>
<li>Batch processing patterns</li>
<li>Stream processing patterns</li>
<li>Metrics that actually tell you something</li>
</ul>
<p><strong>Open source</strong></p>
<ul>
<li>Measuring your open source project&rsquo;s success</li>
<li>Bug squashing done right</li>
<li>Licensing &mdash; what to know before you pick one</li>
<li>Vendor lock-in and how to spot it early</li>
</ul>
<hr>
<p>We pay engineers to share what they&rsquo;ve learned. That&rsquo;s the whole offer. If you&rsquo;ve got something worth writing, write it.</p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/22/write-for-percona-community/">Write for the Percona Community</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MySQL 9.7.0 PGO Benchmark Analysis</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/mysql-9-7-0-pgo-benchmark-analysis/" />
      <id>https://www.percona.com/blog/mysql-9-7-0-pgo-benchmark-analysis/</id>
      <updated>2026-05-22T07:33:49+00:00</updated>
      <author><name>Bogdan Degtyariov</name></author>
      <summary type="html"><![CDATA[<p>Overview Servers Tested: MySQL 9.7.0 (PGO-enabled build released by Oracle) MySQL 9.7.0 Non-PGO (built without Profile-Guided Optimization — see BUILD.md) Tier Configurations: Tier 2G: 2GB InnoDB buffer pool Tier 12G: 12GB InnoDB buffer pool Tier 32G: 32GB InnoDB buffer pool   View Results 📊 Interactive Reports The benchmark reports are available as interactive HTML pages … Continued<br />
The post MySQL 9.7.0 PGO Benchmark Analysis appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/mysql-9-7-0-pgo-benchmark-analysis/">MySQL 9.7.0 PGO Benchmark Analysis</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<h2><b>Overview</b><a class="anchor-link" id="overview"></a></h2>
<p><b>Servers Tested:</b></p>
<ul>
<li style="font-weight: 400"><b>MySQL 9.7.0</b><span style="font-weight: 400"> (PGO-enabled build released by Oracle)</span></li>
<li style="font-weight: 400"><b>MySQL 9.7.0 Non-PGO</b><span style="font-weight: 400"> (built without Profile-Guided Optimization &mdash; see </span><a href="https://github.com/Percona-Lab-results/2026-pgo/blob/main/BUILD.md"><span style="font-weight: 400">BUILD.md</span></a><span style="font-weight: 400">)</span></li>
</ul>
<p><b>Tier Configurations:</b></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Tier 2G: 2GB InnoDB buffer pool</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Tier 12G: 12GB InnoDB buffer pool</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Tier 32G: 32GB InnoDB buffer pool</span></li>
</ul>
<p>&nbsp;</p>
<h2><b>View Results</b><a class="anchor-link" id="view-results"></a></h2>
<p><span style="font-weight: 400"><img decoding="async" src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4ca.png" alt="&#128202;" class="wp-smiley" style="height: 1em;max-height: 1em"> </span><a href="https://percona-lab-results.github.io/2026-pgo/index.html"><b>Interactive Reports</b></a></p>
<p><span style="font-weight: 400">The benchmark reports are available as interactive HTML pages at:</span></p>
<p><a href="https://percona-lab-results.github.io/2026-pgo/index.html" target="_blank" rel="noopener"><span style="font-weight: 400">https://percona-lab-results.github.io/2026-pgo/index.html</span></a></p>
<h3><b>Performance Graphs</b><a class="anchor-link" id="performance-graphs"></a></h3>
<p><b>Tier 2G (2GB Buffer Pool):</b></p>
<p><b><a href="https://percona-lab-results.github.io/2026-pgo/sysbench_ps_mysql_average.html"><img decoding="async" loading="lazy" class="alignnone wp-image-47783 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/pgo_2G-1024x495.png" alt="" width="1024" height="495"></a></b></p>
<p><b>Tier 12G (12GB Buffer Pool):</b></p>
<p><b> <a href="https://percona-lab-results.github.io/2026-pgo/sysbench_ps_mysql_average.html"><img decoding="async" loading="lazy" class="alignnone wp-image-47784 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/pgo_12G-1024x494.png" alt="" width="1024" height="494"></a></b></p>
<p><b>Tier 32G (32GB Buffer Pool):</b></p>
<p>&nbsp;</p>
<p><b> <a href="https://percona-lab-results.github.io/2026-pgo/sysbench_ps_mysql_average.html"><img decoding="async" loading="lazy" class="alignnone wp-image-47785 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/pgo_32G-1024x494.png" alt="" width="1024" height="494"></a></b></p>
<p>&nbsp;</p>
<h2><b>Key Findings</b><a class="anchor-link" id="key-findings"></a></h2>
<h3><b><br>
Performance Impact of PGO</b><a class="anchor-link" id="performance-impact-of-pgo"></a></h3>
<p><span style="font-weight: 400">MySQL 9.7.0 with Profile-Guided Optimization (PGO) demonstrates measurable performance improvements over the non-PGO build:</span></p>
<p><b><br>
Overall Performance Summary:</b></p>
<ul>
<li style="font-weight: 400"><b>Average improvement: 6.5%</b><span style="font-weight: 400"> across all configurations</span></li>
<li style="font-weight: 400"><b>Peak improvement: 14.3%</b><span style="font-weight: 400"> (Tier 32G, 1 thread), gradually tapering to 10.3% at 512 threads as concurrency increases</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Performance gains range from 0.5% to 14.3% in most scenarios</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Minor regression (-3.1% at Tier 12G, 128 threads)</span></li>
</ul>
<p><b><br>
Performance by Buffer Pool Size:</b></p>
<ul>
<li style="font-weight: 400"><b>Tier 2G</b><span style="font-weight: 400"> (2GB buffer pool): Average improvement of </span><b>3.0%</b></li>
</ul>
<p><span style="font-weight: 400">&ndash; Best gains at 4 threads (5.5% improvement)</span></p>
<p><span style="font-weight: 400">&ndash; Gains range from 0.5% to 5.5% across all thread counts</span></p>
<p><span style="font-weight: 400">&ndash; Modest improvements with no regressions</span></p>
<ul>
<li style="font-weight: 400"><b>Tier 12G</b><span style="font-weight: 400"> (12GB buffer pool): Average improvement of </span><b>4.1%</b></li>
</ul>
<p><span style="font-weight: 400">&ndash; Best gains at 4 threads (8.6% improvement)</span></p>
<p><span style="font-weight: 400">&ndash; Strong gains at low concurrency (1-4 threads: 7.3%-8.6%)</span></p>
<p><span style="font-weight: 400">&ndash; Minor regression at 128 threads (-3.1%), neutral at 512 threads (-0.0%)</span></p>
<ul>
<li style="font-weight: 400"><b>Tier 32G</b><span style="font-weight: 400"> (32GB buffer pool): Average improvement of </span><b>12.2%</b></li>
</ul>
<p><span style="font-weight: 400">&ndash; Consistently strong gains across all thread counts (10.3% to 14.3%)</span></p>
<p><span style="font-weight: 400">&ndash; Peak performance at lowest concurrency (1 thread: 14.3%)</span></p>
<p><span style="font-weight: 400">&ndash; Maintains 11-12% improvement even at highest concurrency (128-512 threads)</span></p>
<p><b><br>
Key Observations:</b></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">PGO provides the most significant benefits with larger buffer pools (32GB tier shows 12.2% average improvement)</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Largest buffer pool configuration benefits from PGO across all concurrency levels with no regressions</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Low to moderate concurrency (1-32 threads) shows best PGO gains across all tiers</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Smaller buffer pools (2GB, 12GB) show more modest improvements and occasional regressions at very high thread counts</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The performance improvements demonstrate PGO&rsquo;s effectiveness in optimizing hot code paths, particularly when memory resources are abundant</span></li>
</ul>
<h3><b><br>
InnoDB Metrics Analysis</b><a class="anchor-link" id="innodb-metrics-analysis"></a></h3>
<p><span style="font-weight: 400">Deep analysis of InnoDB metrics reveals the source of PGO&rsquo;s performance improvements:</span></p>
<p><b><br>
Root Cause: CPU-Level Optimizations</b></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">PGO improvements are </span><b>NOT</b><span style="font-weight: 400"> from I/O optimization, caching, or lock reduction</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Buffer pool hit ratios remain virtually identical between PGO and non-PGO builds</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Lock contention is minimal in both builds</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">All I/O metrics scale proportionally with increased throughput</span></li>
</ul>
<p><b><br>
What PGO Actually Optimizes:</b></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">&#10003; Better instruction cache utilization</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">&#10003; Improved branch prediction in hot code paths</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">&#10003; Optimized function inlining</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">&#10003; More efficient CPU instruction ordering</span></li>
</ul>
<p><span style="font-weight: 400">The metrics confirm that PGO&rsquo;s 6.5% average improvement comes entirely from making the CPU more efficient at executing MySQL&rsquo;s hot code paths, allowing it to process more transactions per second with the same hardware resources.</span></p>
<p>&nbsp;</p>
<h2><b>What is PGO?</b><a class="anchor-link" id="what-is-pgo"></a></h2>
<p><span style="font-weight: 400">Profile-Guided Optimization (PGO) is a compiler optimization technique that uses runtime profiling data to guide code optimization. The compiler first instruments the code, collects execution profiles during typical workload runs, and then recompiles the code with optimizations targeted at the most frequently executed code paths.</span></p>
<p><b><br>
Benefits of PGO:</b></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Improved branch prediction</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Better instruction cache utilization</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Optimized function inlining</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Reduced code bloat</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Better register allocation</span></li>
</ul>
<p>&nbsp;</p>
<h2><b>Benchmark Methodology</b><a class="anchor-link" id="benchmark-methodology"></a></h2>
<h3><b><br>
Workload</b><a class="anchor-link" id="workload"></a></h3>
<ul>
<li style="font-weight: 400"><b>Tool</b><span style="font-weight: 400">: Sysbench OLTP Read/Write benchmark</span></li>
<li style="font-weight: 400"><b>Tables</b><span style="font-weight: 400">: 20 tables</span></li>
<li style="font-weight: 400"><b>Table Size</b><span style="font-weight: 400">: 5,000,000 rows per table</span></li>
<li style="font-weight: 400"><b>Thread Counts</b><span style="font-weight: 400">: 1, 4, 16, 32, 64, 128, 256, 512</span></li>
</ul>
<h3><b><br>
Configuration</b><a class="anchor-link" id="configuration"></a></h3>
<ul>
<li style="font-weight: 400"><b>Warmup</b><span style="font-weight: 400">:</span></li>
</ul>
<p><span style="font-weight: 400">&ndash; Read-only: 180 seconds</span></p>
<p><span style="font-weight: 400">&ndash; Read-write: 600 seconds</span></p>
<ul>
<li style="font-weight: 400"><b>Measurement Duration</b><span style="font-weight: 400">: 900 seconds (15 minutes) per thread count</span></li>
<li style="font-weight: 400"><b>Runs</b><span style="font-weight: 400">: Single run per configuration</span></li>
</ul>
<h3><b><br>
System Metrics Collected</b><a class="anchor-link" id="system-metrics-collected"></a></h3>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">InnoDB storage engine metrics</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">MySQL status variables</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">MySQL system variables</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">System I/O statistics (iostat)</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Virtual memory statistics (vmstat)</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">CPU statistics (mpstat)</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">System statistics (dstat)</span></li>
</ul>
<p>&nbsp;</p>
<h2><b>Appendix</b><a class="anchor-link" id="appendix"></a></h2>
<p><span style="font-weight: 400">For Repository structure, Build steps and Technical details go to </span><a href="https://github.com/Percona-Lab-results/2026-pgo/blob/main/README.md#report-categories"><span style="font-weight: 400">https://github.com/Percona-Lab-results/2026-pgo/blob/main/README.md#report-categories</span></a></p>
<p>&nbsp;</p>
<p>The post <a href="https://www.percona.com/blog/mysql-9-7-0-pgo-benchmark-analysis/">MySQL 9.7.0 PGO Benchmark Analysis</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/mysql-9-7-0-pgo-benchmark-analysis/">MySQL 9.7.0 PGO Benchmark Analysis</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Knowing when new open source database engine versions release on Amazon Aurora and Amazon RDS</title>
      <link rel="alternate" type="text/html" href="https://aws.amazon.com/blogs/database/knowing-when-new-open-source-database-engine-versions-release-on-amazon-aurora-and-amazon-rds/" />
      <id>https://aws.amazon.com/blogs/database/knowing-when-new-open-source-database-engine-versions-release-on-amazon-aurora-and-amazon-rds/</id>
      <updated>2026-05-21T18:36:46+00:00</updated>
      <author><name>Betty Chun</name></author>
      <summary type="html"><![CDATA[<p>In this post, we share the version currency timelines for Aurora and RDS open source engines. We also explain why timelines differ across engines and how you can use them to plan your upgrades.</p>
<p>The post <a rel="nofollow" href="https://aws.amazon.com/blogs/database/knowing-when-new-open-source-database-engine-versions-release-on-amazon-aurora-and-amazon-rds/">Knowing when new open source database engine versions release on Amazon Aurora and Amazon RDS</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>If you&rsquo;re running or considering <a href="https://aws.amazon.com/rds/aurora/" target="_blank" rel="noopener">Amazon Aurora</a> with PostgreSQL or MySQL compatibility, you&rsquo;ve likely wondered, &ldquo;When will the latest community version be available on AWS?&rdquo; The same question applies if you run <a href="https://aws.amazon.com/rds/" target="_blank" rel="noopener">Amazon Relational Database Service</a> (Amazon RDS) for PostgreSQL, MySQL, or MariaDB. Whether you want the newest features quickly or prefer to standardize on stable long-term support (LTS) versions, our release timelines help you plan upgrades and maintenance cycles. In this post, we share the version currency timelines for Aurora and RDS open source engines. We also explain why timelines differ across engines and how you can use them to plan your upgrades.</p>
<p>Today, we are publishing version currency timelines for Aurora and RDS open source engines. The timelines apply to new major and minor versions going forward and define when you and your teams can expect new versions on AWS. With this predictability, you can plan maintenance windows, upgrade cycles, and Aurora LTS adoption for workloads that prioritize long-term stability.</p>
<table border="1px" cellpadding="10px" width="100%">
<tbody>
<tr style="background-color: black;color: white">
<td><strong>Database engine</strong></td>
<td><strong>Release type</strong></td>
<td><strong>Timeline</strong></td>
</tr>
<tr>
<td rowspan="2"><strong><a href="https://docs.aws.amazon.com/AmazonRDS/latest/PostgreSQLReleaseNotes/postgresql-release-calendar.html" rel="noopener" target="_blank">RDS for PostgreSQL</a></strong></td>
<td>Minor versions</td>
<td>Within 7 days of community release</td>
</tr>
<tr>
<td>Major versions</td>
<td>Within 30 days of the community <code>&lt;major&gt;.1</code> release</td>
</tr>
<tr>
<td rowspan="2"><strong><a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/MySQL.Concepts.VersionMgmt.html" rel="noopener" target="_blank">RDS for MySQL</a></strong></td>
<td>Minor versions</td>
<td>Within 30 days of community release</td>
</tr>
<tr>
<td>Major versions</td>
<td>Within 6 months of community <code>&lt;major&gt;.1</code> release (Oracle MySQL LTS majors)</td>
</tr>
<tr>
<td rowspan="2"><strong><a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/MariaDB.Concepts.VersionMgmt.html" rel="noopener" target="_blank">RDS for MariaDB</a></strong></td>
<td>Minor versions</td>
<td>Within 30 days of community release</td>
</tr>
<tr>
<td>Major versions</td>
<td>Within 3 months of community&rsquo;s first patch release</td>
</tr>
<tr>
<td rowspan="3"><strong><a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraPostgreSQLReleaseNotes/aurorapostgresql-release-calendar.html" rel="noopener" target="_blank">Aurora PostgreSQL</a></strong></td>
<td>Minor versions</td>
<td>Within 3 months of community release</td>
</tr>
<tr>
<td>Major versions</td>
<td>Within 8 months of the community <code>&lt;major&gt;.1</code> release</td>
</tr>
<tr>
<td>Aurora LTS per major</td>
<td>Within 12 months of Aurora major GA</td>
</tr>
<tr>
<td rowspan="3"><strong><a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraMySQLReleaseNotes/AuroraMySQL.release-calendars.html" rel="noopener" target="_blank">Aurora MySQL</a></strong></td>
<td>Minor versions</td>
<td>Within 3 months of community release</td>
</tr>
<tr>
<td>Major versions</td>
<td>Within 12 months of community <code>&lt;major&gt;.1</code> release (Oracle MySQL LTS majors)</td>
</tr>
<tr>
<td>Aurora LTS per major</td>
<td>Within 12 months of Aurora major GA</td>
</tr>
</tbody>
</table>
<p>For the current schedule of upcoming and recently shipped versions, including specific version numbers and target dates, see the release calendar linked from each engine name in the table. </p>
<h2>Why timelines differ across engines<a class="anchor-link" id="why-timelines-differ-across-engines"></a></h2>
<p>The timelines differ by engine because the upstream development and integration models differ. PostgreSQL and MariaDB communities develop in the open, which lets us start validation early. MySQL commits are available closer to public releases. RDS runs the community engine on managed infrastructure, so after a community release passes validation it can ship quickly. Aurora adds a distributed storage layer, Global Database, and serverless capabilities underneath PostgreSQL- and MySQL-compatible engines. Every new version goes through additional validation to verify that those capabilities continue to function correctly. This is why Aurora timelines are longer than RDS timelines for the same engine. Aurora also offers Long-Term Support releases for multi-year stability on a single minor version.</p>
<h2>How we choose major version starting points<a class="anchor-link" id="how-we-choose-major-version-starting-points"></a></h2>
<p>For PostgreSQL, our first production release of a new major version is typically based on the community <code>&lt;major&gt;.1</code> release rather than <code>&lt;major&gt;.0</code>. The <code>&lt;major&gt;.1</code> release generally arrives roughly three months after the initial major release. It incorporates the first round of bug fixes and security patches identified during early production deployments, which provides a more stable starting point for production workloads.</p>
<p>MariaDB follows a similar pattern. The published major version timelines are measured from the community&rsquo;s first patch release for a new major version rather than the initial <code>&lt;major&gt;.0</code> release. This gives customers a more mature production baseline to target.</p>
<p>For MySQL, the major version timelines apply to Oracle MySQL LTS major releases and are measured from the corresponding <code>&lt;major&gt;.1</code> release. This aligns the timelines to the first patch release after the initial LTS major becomes generally available.</p>
<h2>What this means for your upgrade planning<a class="anchor-link" id="what-this-means-for-your-upgrade-planning"></a></h2>
<p>Published version currency timelines give you and your teams earlier visibility into release planning and upgrade scheduling. With RDS for PostgreSQL minor versions arriving within 7 days of community release, teams can stay current on security patches and bug fixes with relatively little operational planning. You can enable automatic minor version upgrades to receive patches during maintenance windows. You can also use <a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_upgrade_rollout.html" rel="noopener" target="_blank">AWS Organizations upgrade rollout policies</a> to manage deployment sequencing across your development, test, and production environments, or apply upgrades manually based on your own operational processes.</p>
<p>Earlier visibility into major version timelines helps your teams adopt new database capabilities on your own schedule. Knowing when a new version is expected on Aurora or RDS gives teams more time to review release notes, validate application behavior, and prepare rollout plans ahead of adoption. With RDS Database Preview, you get early access to PostgreSQL and MySQL major versions in a non-production environment so you can test application compatibility in advance. With Blue/Green Deployments, you can validate changes before cutover and transition production traffic with minimal downtime.</p>
<p>With Aurora LTS releases, you can prioritize operational stability over rapid feature adoption. Your teams can remain on a stable minor baseline for multiple years while aligning major version upgrades with broader application and infrastructure roadmaps.</p>
<p>For workloads approaching or beyond community end-of-life timelines, Amazon RDS Extended Support gives you additional time to finish upgrades while continuing to receive critical security updates.</p>
<h2>Learn more<a class="anchor-link" id="learn-more"></a></h2>
<p>For detailed upgrade procedures and release guidance, see the <a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/CHAP_AuroraOverview.html" target="_blank" rel="noopener">Amazon Aurora User Guide</a> and the <a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Welcome.html" target="_blank" rel="noopener">Amazon RDS User Guide</a>.</p>
<hr>
<h2>About the authors<a class="anchor-link" id="about-the-authors"></a></h2>
<footer>
<div class="blog-author-box">
<div class="blog-author-image">
   <img decoding="async" loading="lazy" class="alignleft size-full" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2026/05/18/DB5644a1.jpg" alt="Betty Chun" width="100" height="100">
  </div>
<h3 class="lb-h4">Betty Chun<a class="anchor-link" id="betty-chun"></a></h3>
<p><a target="_blank" href="https://www.linkedin.com/in/betty-chun-3b8811/" rel="noopener">Betty</a> is a Principal Product Marketing Manager at AWS. She focuses on relational database services, such as Amazon Aurora. She is based on Seattle and enjoys cooking and the outdoors.</p>
</div>
<div class="blog-author-box">
<div class="blog-author-image">
   <img decoding="async" loading="lazy" class="aligncenter size-full wp-image-29797" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2025/11/24/DBBLOG-5173-14.png" alt="Keyur Diwan" width="120" height="160">
  </div>
<h3 class="lb-h4">Keyur Diwan<a class="anchor-link" id="keyur-diwan"></a></h3>
<p><a href="https://www.linkedin.com/in/keyurdiwan/" target="_blank" rel="noopener">Keyur</a> is a Principal Product Manager with Amazon Aurora/RDS in Seattle, where he builds next-generation capabilities in managed PostgreSQL, Blue/Green deployments, seamless upgrades, security, and analytics technologies such as HTAP, ZETL, and CDC streaming.</p>
</div>
<div class="blog-author-box">
<div class="blog-author-image">
   <img decoding="async" loading="lazy" class="aligncenter size-full wp-image-29797" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2025/12/11/DBBLOG-5252-2.jpeg" alt="Abhinav Dhandh" width="120" height="160">
  </div>
<h3 class="lb-h4">Abhinav Dhandh<a class="anchor-link" id="abhinav-dhandh"></a></h3>
<p><a target="_blank" href="https://www.linkedin.com/in/abhinav-dhandh-35139418/" rel="noopener">Abhinav</a> is a product management leader for Amazon Aurora and Amazon RDS Open Source databases. He is responsible for driving developer experiences, agentic AI, and scale out charter. Abhinav has led critical initiatives for managed open source database strategy, zero-ETL and change data capture (CDC), migrations, and commitment-based pricing.</p>
</div>
</footer>

<p>The post <a rel="nofollow" href="https://aws.amazon.com/blogs/database/knowing-when-new-open-source-database-engine-versions-release-on-amazon-aurora-and-amazon-rds/">Knowing when new open source database engine versions release on Amazon Aurora and Amazon RDS</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>AI-Assisted Production Database Ops with ClusterControl MCP and CCX MCP</title>
      <link rel="alternate" type="text/html" href="https://severalnines.com/blog/ai-assisted-production-database-ops-with-clustercontrol-mcp-and-ccx-mcp/" />
      <id>https://severalnines.com/blog/ai-assisted-production-database-ops-with-clustercontrol-mcp-and-ccx-mcp/</id>
      <updated>2026-05-21T10:22:01+00:00</updated>
      <author><name>Kyle Buzzell</name></author>
      <summary type="html"><![CDATA[<p>In December, we introduced how Model Context Protocol could make ClusterControl easier to work with from AI assistants. Since then, Severalnines has expanded that MCP direction across its database operations platforms with ClusterControl MCP and CCX MCP. The latest ClusterControl MCP is the major update, providing a more robust implementation with 69 tools and 20 […]<br />
The post AI-Assisted Production Database Ops with ClusterControl MCP and CCX MCP appeared first on Severalnines.</p>
<p>The post <a rel="nofollow" href="https://severalnines.com/blog/ai-assisted-production-database-ops-with-clustercontrol-mcp-and-ccx-mcp/">AI-Assisted Production Database Ops with ClusterControl MCP and CCX MCP</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>In December, we introduced how Model Context Protocol could make <a href="https://severalnines.com/clustercontrol">ClusterControl</a> easier to work with from AI assistants. Since then, Severalnines has expanded that MCP direction across its database operations platforms with <strong>ClusterControl MCP</strong> and <strong>CCX MCP</strong>.</p>
<p>The latest ClusterControl MCP is the major update, providing a more robust implementation with <strong>69 tools and 20 MCP resources / templates</strong> for production database operations across ClusterControl-managed environments. CCX MCP is the companion MCP server for <a href="https://severalnines.com/ccx">CCX</a>, bringing AI-assisted workflows to managed cloud database operations.</p>
<p>Together, they give Severalnines users a practical way to inspect, troubleshoot, and act on database infrastructure from MCP-compatible clients such as Claude Desktop, Claude Code, OpenAI Codex, and other tools that support MCP.</p>
<h2 class="wp-block-heading" id="h-what-is-new-in-clustercontrol-mcp">What is new in ClusterControl MCP?<a class="anchor-link" id="what-is-new-in-clustercontrol-mcp"></a></h2>
<p>ClusterControl MCP 1.0 moves beyond the earlier MCP concept and provides broader coverage across daily database operations. You can ask questions such as:</p>
<ul class="wp-block-list">
<li>&ldquo;List all my database clusters and their status.&rdquo;</li>
<li>&ldquo;Show me the topology of cluster 2.&rdquo;</li>
<li>&ldquo;Are there any active alarms across all clusters?&rdquo;</li>
<li>&ldquo;What backup jobs have run on cluster 3?&rdquo;</li>
<li>&ldquo;Show me the top queries by wait time on cluster 1.&rdquo;</li>
<li>&ldquo;Are there any tables without primary keys?&rdquo;</li>
<li>&ldquo;Show me recent transaction deadlocks.&rdquo;</li>
<li>&ldquo;List the log files collected from cluster 1.&rdquo;</li>
<li>&ldquo;Who made changes to cluster 3 in the last hour?&rdquo;</li>
</ul>
<p>You can also prepare actions such as:</p>
<ul class="wp-block-list">
<li>&ldquo;Run a backup on cluster 1 right now.&rdquo;</li>
<li>&ldquo;Create a nightly backup schedule at 02:00.&rdquo;</li>
<li>&ldquo;Put db1.example.com into maintenance from 22:00 to 23:00 UTC.&rdquo;</li>
<li>&ldquo;Create a read-only database user for reporting.&rdquo;</li>
<li>&ldquo;Set max_connections to 500 on db1.example.com.&rdquo;</li>
<li>&ldquo;Restore backup #42 to cluster 1.&rdquo;</li>
</ul>
<p>Write operations use a dry-run-first model. The assistant describes what would happen before anything is executed, and high-risk operations include extra warnings.</p>
<h2 class="wp-block-heading" id="h-example-move-from-alarm-to-evidence-faster">Example: move from alarm to evidence faster<a class="anchor-link" id="example-move-from-alarm-to-evidence-faster"></a></h2>
<p>A common operational flow starts with a broad question:</p>
<p>&ldquo;Are there any active alarms across all clusters?&rdquo;</p>
<p>From there, you can drill down:</p>
<ul class="wp-block-list">
<li>&ldquo;Show me alarms for cluster 3.&rdquo;</li>
<li>&ldquo;Show me the CMON log for cluster 3 from the last hour.&rdquo;</li>
<li>&ldquo;Summarize the warnings by component and hostname.&rdquo;</li>
</ul>
<p>That is where the 1.0 implementation becomes useful. It is not just returning a static dashboard view. It can help you move across related operational data: alarms, jobs, CMON controller logs, database server logs, topology, backup history, maintenance windows, and audit events.</p>
<h2 class="wp-block-heading" id="h-example-inspect-and-manage-backups-conversationally">Example: inspect and manage backups conversationally<a class="anchor-link" id="example-inspect-and-manage-backups-conversationally"></a></h2>
<p>Backups are another area where ClusterControl MCP 1.0 adds practical coverage. You can ask:</p>
<ul class="wp-block-list">
<li>&ldquo;When was the last successful backup on my MongoDB cluster?&rdquo;</li>
<li>&ldquo;Show me only failed backups on cluster 1.&rdquo;</li>
<li>&ldquo;Does cluster 1 have a backup schedule configured?&rdquo;</li>
</ul>
<p>And then prepare a change:</p>
<p>&ldquo;Create a nightly backup schedule at 02:00 on cluster 1 using xtrabackup.&rdquo;</p>
<p>The assistant first returns a dry-run preview. Only after confirmation does it execute the change.</p>
<h2 class="wp-block-heading" id="h-installing-clustercontrol-mcp">Installing ClusterControl MCP<a class="anchor-link" id="installing-clustercontrol-mcp"></a></h2>
<p>ClusterControl MCP packages are published through the Severalnines repository alongside other ClusterControl components.</p>
<p>Debian / Ubuntu:</p>
<pre class="wp-block-code"><code>apt-get install clustercontrol-mcp</code></pre>
<p>RHEL / Rocky / AlmaLinux:</p>
<pre class="wp-block-code"><code>yum install clustercontrol-mcp</code></pre>
<p>The binary installs to:</p>
<pre class="wp-block-code"><code>/usr/bin/cmon-mcp</code></pre>
<p>The package also installs:</p>
<pre class="wp-block-code"><code>/etc/systemd/system/cmon-mcp.service
/etc/default/cmon-mcp</code></pre>
<h2 class="wp-block-heading" id="h-setting-up-clustercontrol-mcp-in-stdio-mode">Setting up ClusterControl MCP in stdio mode<a class="anchor-link" id="setting-up-clustercontrol-mcp-in-stdio-mode"></a></h2>
<p>First, we&rsquo;ll start with stdio mode for when the AI client runs the MCP server locally, such as Claude Desktop or Claude Code.</p>
<p>Claude Desktop configuration:</p>
<pre class="wp-block-code"><code>{
 "mcpServers": {
   "clustercontrol": {
     "command": "cmon-mcp",
     "env": {
       "CMON_ENDPOINT": "https://your-cc-host:9501",
       "CMON_USERNAME": "admin",
       "CMON_PASSWORD": "your-password"
     }
   }
 }
}</code></pre>
<p>Restart Claude Desktop. The hammer icon confirms that the MCP server loaded.</p>
<p>For Claude Code:</p>
<pre class="wp-block-code"><code>claude mcp add clustercontrol -- cmon-mcp 
 -endpoint https://your-cc-host:9501 
 -username admin 
 -password your-password</code></pre>
<h2 class="wp-block-heading" id="h-setting-up-clustercontrol-mcp-in-http-mode">Setting up ClusterControl MCP in HTTP mode<a class="anchor-link" id="setting-up-clustercontrol-mcp-in-http-mode"></a></h2>
<p>Use HTTP mode for OpenAI Codex, team access, or multi-client access. Edit:</p>
<pre class="wp-block-code"><code>/etc/default/cmon-mcp</code></pre>
<p>Example:</p>
<pre class="wp-block-code"><code>CMON_ENDPOINT=https://127.0.0.1:9501
CMON_USERNAME=admin
CMON_KEY_FILE=/etc/clustercontrol/id_rsa

MCP_BIND_ADDRESS=0.0.0.0:3000
MCP_BASE_URL=http://your-cc-host:3000
MCP_AUTH_TOKEN=&lt;your-strong-random-token&gt;</code></pre>
<p>Generate a strong token:</p>
<pre class="wp-block-code"><code>openssl rand -hex 32</code></pre>
<p>Restart the service:</p>
<pre class="wp-block-code"><code>systemctl restart cmon-mcp
journalctl -u cmon-mcp -n 20</code></pre>
<p>Connect OpenAI Codex:</p>
<pre class="wp-block-code"><code>codex --mcp-server-uri http://your-cc-host:3000/mcp 
     --mcp-header "Authorization: Bearer &lt;your-token&gt;"</code></pre>
<p>Connect Claude Code over SSE:</p>
<pre class="wp-block-code"><code>claude mcp add clustercontrol --transport sse http://your-cc-host:3000/sse 
 --header "Authorization: Bearer &lt;your-token&gt;"</code></pre>
<p>Connect Claude Desktop over SSE:</p>
<pre class="wp-block-code"><code>{
 "mcpServers": {
   "clustercontrol": {
     "type": "sse",
     "url": "http://your-cc-host:3000/sse",
     "headers": {
       "Authorization": "Bearer &lt;your-token&gt;"
     }
   }
 }
}</code></pre>
<h2 class="wp-block-heading" id="h-ccx-mcp-ai-assisted-workflows-for-ccx">CCX MCP: AI-Assisted Workflows for CCX<a class="anchor-link" id="ccx-mcp-ai-assisted-workflows-for-ccx"></a></h2>
<p>As noted upfront, <strong>CCX MCP</strong> brings the MCP-based workflow to Severalnines users running managed cloud databases in CCX. It lets MCP-compatible AI clients interact with CCX datastores, cloud providers, plans, databases, users, firewall rules, backups, parameter groups, and performance data.</p>
<p>Typical prompts include:</p>
<ul class="wp-block-list">
<li>&ldquo;List my datastores.&rdquo;</li>
<li>&ldquo;Create a PostgreSQL cluster.&rdquo;</li>
<li>&ldquo;Get the connection string for my production database.&rdquo;</li>
<li>&ldquo;Add 10.0.0.0/24 as a trusted source.&rdquo;</li>
<li>&ldquo;Show me the slowest queries.&rdquo;</li>
<li>&ldquo;List available backups for this datastore.&rdquo;</li>
</ul>
<p>CCX MCP supports PostgreSQL, MySQL / Percona, MariaDB, Redis, Valkey, and Microsoft SQL Server. It also includes protection behavior for destructive operations, which are blocked by default unless protection is explicitly disabled.</p>
<h2 class="wp-block-heading" id="h-installing-and-setting-up-ccx-mcp">Installing and setting up CCX MCP<a class="anchor-link" id="installing-and-setting-up-ccx-mcp"></a></h2>
<p>CCX MCP can be installed from npm:</p>
<pre class="wp-block-code"><code>npm install @severalnines/ccx-mcp</code></pre>
<p>Or used directly through <code>npx</code> in your MCP client configuration:</p>
<pre class="wp-block-code"><code>{
 "mcpServers": {
   "ccx": {
     "command": "npx",
     "args": ["-y", "@severalnines/ccx-mcp"],
     "env": {
       "CCX_BASE_URL": "https://app.myccx.io",
       "CCX_USERNAME": "your-email@example.com",
       "CCX_PASSWORD": "your-password"
     }
   }
 }
}</code></pre>
<p>OAuth2 is also supported:</p>
<pre class="wp-block-code"><code>{
 "CCX_CLIENT_ID": "your-client-id",
 "CCX_CLIENT_SECRET": "your-client-secret"
}</code></pre>
<h3 class="wp-block-heading" id="h-claude-code">Claude Code<a class="anchor-link" id="claude-code"></a></h3>
<p>For Claude Code, register the CCX MCP server in one command. No manual config file editing is required:</p>
<pre class="wp-block-code"><code>claude mcp add ccx -- npx -y @severalnines/ccx-mcp@latest 
 --endpoint https://app.myccx.io 
 --client-id &lt;your-client-id&gt; 
 --client-secret &lt;your-client-secret&gt;</code></pre>
<p>Create OAuth2 credentials in the CCX UI under <strong>Account &gt; Security</strong>.</p>
<p>Then restart Claude Code, or run <code>/mcp</code> and reconnect. After that, you are ready to start using CCX MCP from your Claude Code session.</p>
<h2 class="wp-block-heading" id="h-wrapping-up">Wrapping up<a class="anchor-link" id="wrapping-up"></a></h2>
<p>The original ClusterControl MCP work showed how AI assistants could become useful in database operations when connected to the right operational context. The latest version makes that idea much more complete, providing a broader and safer operational interface across clusters, jobs, alarms, backups, logs, performance, users, maintenance, audit, and configuration. <a href="https://docs.severalnines.com/clustercontrol/latest/reference-manuals/clustercontrol-mcp/">Go here for more information on how it works and detailed documentation.</a></p>
<p>For CCX and service provider users, CCX MCP provides the companion interface for managed cloud database operations. Together, they give database teams a practical way to use AI where it matters: inside real operational workflows, with context, control, and safety.</p>
<p>The post <a href="https://severalnines.com/blog/ai-assisted-production-database-ops-with-clustercontrol-mcp-and-ccx-mcp/">AI-Assisted Production Database Ops with ClusterControl MCP and CCX MCP</a> appeared first on <a href="https://severalnines.com">Severalnines</a>.</p>

<p>The post <a rel="nofollow" href="https://severalnines.com/blog/ai-assisted-production-database-ops-with-clustercontrol-mcp-and-ccx-mcp/">AI-Assisted Production Database Ops with ClusterControl MCP and CCX MCP</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Vibe-coding an Audit Plugin in Under 3 Minutes</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/vibe-coding-an-audit-plugin-in-under-3-minutes/" />
      <id>https://mariadb.org/vibe-coding-an-audit-plugin-in-under-3-minutes/</id>
      <updated>2026-05-21T09:06:36+00:00</updated>
      <author><name>Georgi Kodinov</name></author>
      <summary type="html"><![CDATA[<p>Who says developing MariaDB plugins is hard? I was able to produce one in under 3 minutes!<br />
I of course did it by asking Grok nicely:<br />
The produced result is actually very decent:<br />
It even produced a Makefile:<br />
And compilation instructions:<br />
While I had Grok’s attention I’ve given it a follow up task:<br />
Note the “please” …<br />
Continue reading \"Vibe-coding an Audit Plugin in Under 3 Minutes\"<br />
The post Vibe-coding an Audit Plugin in Under 3 Minutes appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/vibe-coding-an-audit-plugin-in-under-3-minutes/">Vibe-coding an Audit Plugin in Under 3 Minutes</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Who says developing MariaDB plugins is hard? I was able to produce one in under 3 minutes!<br>
I of course did it by asking Grok nicely:<br>
The produced result is actually very decent:<br>
It even produced a Makefile:<br>
And compilation instructions:<br>
While I had Grok&rsquo;s attention I&rsquo;ve given it a follow up task:<br>
Note the &ldquo;please&rdquo; &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/vibe-coding-an-audit-plugin-in-under-3-minutes/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Vibe-coding an Audit Plugin in Under 3 Minutes&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/vibe-coding-an-audit-plugin-in-under-3-minutes/">Vibe-coding an Audit Plugin in Under 3 Minutes</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/vibe-coding-an-audit-plugin-in-under-3-minutes/">Vibe-coding an Audit Plugin in Under 3 Minutes</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Stop Paying for Air: Most Cloud Database Spend Is Wasted</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/stop-paying-for-air-most-cloud-database-spend-is-wasted/" />
      <id>https://mariadb.com/resources/blog/stop-paying-for-air-most-cloud-database-spend-is-wasted/</id>
      <updated>2026-05-20T15:58:06+00:00</updated>
      <author><name>Fermi Fang</name></author>
      <summary type="html"><![CDATA[<p>This blog was originally published on SkySQL’s website. The Hidden Cost of Idle Cloud Databases Structured databases are amongst the biggest […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/stop-paying-for-air-most-cloud-database-spend-is-wasted/">Stop Paying for Air: Most Cloud Database Spend Is Wasted</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>This blog was originally published on SkySQL&rsquo;s website. Structured databases are amongst the biggest line items in enterprise IT infrastructure &ndash; and a significant share of that spend is simply wasted. According to Harness&rsquo;s FinOps in Focus 2025 report, an estimated 21% of enterprise cloud infrastructure spend &ndash; equivalent to $44.5 billion in 2025 &ndash; goes to&hellip;</p>
<p><a href="https://mariadb.com/resources/blog/stop-paying-for-air-most-cloud-database-spend-is-wasted/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/stop-paying-for-air-most-cloud-database-spend-is-wasted/">Stop Paying for Air: Most Cloud Database Spend Is Wasted</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>CVE-2026-8053: “We don’t use time-series” is not a mitigation</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/cve-2026-8053-we-dont-use-time-series-is-not-a-mitigation/" />
      <id>https://www.percona.com/blog/cve-2026-8053-we-dont-use-time-series-is-not-a-mitigation/</id>
      <updated>2026-05-20T15:13:41+00:00</updated>
      <author><name>Radoslaw Szulgo</name></author>
      <summary type="html"><![CDATA[<p>TL;DR: A bug in MongoDB’s time-series collection code allows a user with the standard readWrite role to corrupt memory within the mongod process. Best case: your database crashes, and you spend the night writing a postmortem. Worst case: an attacker is running their code as mongod, with the same access to your data that the … Continued<br />
The post CVE-2026-8053: “We don’t use time-series” is not a mitigation appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/cve-2026-8053-we-dont-use-time-series-is-not-a-mitigation/">CVE-2026-8053: “We don’t use time-series” is not a mitigation</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><strong>TL;DR:</strong> A bug in MongoDB&rsquo;s time-series collection code allows a user with the standard <code>readWrite</code><br>
role to corrupt memory within the <code>mongod</code> process. Best case: your database crashes, and you spend the night writing a postmortem. Worst case: an attacker is running their code as <code>mongod</code>, with the same access to your data that the database process itself has &mdash; every collection on that node, every index, every secret stored in it. The patch for Percona Server for MongoDB 7.0 is already available; 8.0 will be available tomorrow, and 6.0 will be available early next week.</p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-47736 size-full" src="https://www.percona.com/wp-content/uploads/2026/05/blog-hero-CVE-2026-8053.png" alt="" width="1600" height="900"></p>
<p>Every time a bug like this lands, the same conversation plays out in incident channels across the industry. <em>Are we affected? We don&rsquo;t even use time-series collections!</em>&nbsp;Heads nod. Everyone moves on.</p>
<p><strong>That&rsquo;s the mistake.</strong></p>
<p>CVE-2026-8053 is an out-of-bounds memory write in MongoDB&rsquo;s time-series collection &mdash; specifically in the internal mapping between measurement field names and column indexes. Under the right input, the mapping drifts out of sync with the underlying buffer and <code>mongod</code> writes off the end of an allocation. From there, under the right conditions, you can execute arbitrary code as the database process.</p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-47737 size-full" src="https://www.percona.com/wp-content/uploads/2026/05/blog-attack-flow-CVE-2026-8053.png" alt="" width="1600" height="620"></p>
<p>Upstream tracking lives at <a href="https://jira.mongodb.org/browse/SERVER-126021">SERVER-126021</a>. CVSS v3.1 puts it at 8.8. CVSS v4.0 puts it at 8.7. The labels say &ldquo;High.&rdquo; How that &ldquo;High&rdquo; translates into your week depends on a couple of assumptions worth questioning.</p>
<p>Read literally, the prerequisite is &ldquo;an authenticated user with database write privileges.&rdquo; Read operationally, that bar is lower than most teams treat it as.</p>
<h3>The mitigation you think you have doesn&rsquo;t exist<a class="anchor-link" id="the-mitigation-you-think-you-have-doesnt-exist"></a></h3>
<p><span style="font-weight: 400">Modern stacks have dozens of service accounts, with secrets scattered across config files, pipelines, and laptops you&rsquo;ve long forgotten about. Others end up in log files on bad days. And every user with write access to your cluster sits one step away from the vulnerable code path. In a world like that, &ldquo;the attacker would need credentials first&rdquo; isn&rsquo;t a speed bump &mdash; it&rsquo;s a shrug.</span></p>
<p><span style="font-weight: 400">So the real question was never authenticated vs. unauthenticated. It&rsquo;s what authentication unlocks. Here, it unlocks Remote Code Execution (RCE), which is exactly what the CVSS score is trying to tell you &mdash; even if the industry&rsquo;s reaction hasn&rsquo;t quite caught up. Attackers don&rsquo;t need your time-series collection to already exist &ndash; they just need someone&rsquo;s credentials in the wrong hands, and there are more ways for that to happen than most teams want to admit.</span></p>
<p><span style="font-weight: 400">I&rsquo;m not raising this to be smug. I&rsquo;m raising it because too many incident channels keep stalling on the wrong question. It isn&rsquo;t: </span><i><span style="font-weight: 400">&ldquo;Does our app use time-series?&rdquo;</span></i><span style="font-weight: 400"> It&rsquo;s: </span><i><span style="font-weight: 400">&ldquo;What can a user holding our readWrite role actually do this week?&rdquo;</span></i></p>
<p><span style="font-weight: 400">Until you patch, the answer is more than you think.</span></p>
<h3>What Percona is shipping, and when?<a class="anchor-link" id="what-percona-is-shipping-and-when"></a></h3>
<ul>
<li><a href="https://docs.percona.com/percona-server-for-mongodb/7.0/release_notes/7.0.34-19.html"><strong>Percona Server for MongoDB 7.0.34-19</strong></a> &mdash; May 20, 2026</li>
<li><strong>Percona Server for MongoDB 8.0.23-10 </strong>&mdash; May 21, 2026</li>
<li><strong>Percona Server for MongoDB 6.0.28-22</strong> &mdash; May 25, 2026</li>
</ul>
<p>6.0 is on the End-Of-Life (EOL) track. The easy call would be to point at the lifecycle page, note that the upgrade conversation is overdue, and stop there. We&rsquo;re shipping the fix anyway. Customers running 6.0 in production have real reasons they haven&rsquo;t migrated yet &mdash; frozen application stacks, certification cycles, dependencies that don&rsquo;t move on quarterly cadences &mdash; and none of those reasons are worth exploiting while a migration plan gets approved.</p>
<p>Percona is <strong>not</strong> building binary packages for the 5.x line. We&rsquo;re being upfront about that &mdash; the calculus on extended support has a limit, and 5.x is past it for us. But the fix itself is already in our public release branch: <a href="https://github.com/percona/percona-server-mongodb/tree/release-5.0.33-26">release-5.0.33-26</a>. If you have a hard requirement on 5.x and the time pressure to meet it, the source is available for building. Percona customers on 5.x can open a ticket, and we&rsquo;ll work on the case individually.</p>
<h3>What to do this week?<a class="anchor-link" id="what-to-do-this-week"></a></h3>
<p>Patch! Specifically:</p>
<ul>
<li>If you&rsquo;re on <strong>7.0</strong>, upgrade to 7.0.34-19 from <strong>May 20</strong> onward.</li>
<li>If you&rsquo;re on <strong>8.0</strong>, upgrade to 8.0.23-10 from <strong>May 21</strong> onward.</li>
<li>If you&rsquo;re on <strong>6.0</strong>, upgrade to 6.0.28-22 from <strong>May 25</strong> onward.</li>
<li>If you&rsquo;re on <strong>5.0</strong>&nbsp;and you can&rsquo;t move, build from <a href="https://github.com/percona/percona-server-mongodb/tree/release-5.0.33-26">release-5.0.33-26</a>. Customers &mdash; open a ticket and we&rsquo;ll help.</li>
</ul>
<p>As usual, you can download patches from your package manager or Percona <a href="https://www.percona.com/downloads/">Software Downloads</a> page.</p>
<p>If you&rsquo;re running PSMDB <strong>on Kubernetes via the Percona Operator for MongoDB</strong>, edit the image tag in your <code>PerconaServerMongoDB</code> custom resource and let the operator roll the cluster. Don&rsquo;t wait for the June operator release to do it for you. See details in our documentation on how to <a href="https://docs.percona.com/percona-operator-for-mongodb/update-db.html">Upgrade Percona Server for MongoDB</a>.</p>
<p>While you&rsquo;re in there, audit your custom roles. Anything granting <code>createCollection</code> on a production database is, today, an RCE primitive in waiting. Decide whether the service accounts that hold it actually need it. Decide whether your application users need full <code>readWrite</code> or whether a narrower role would do the same job. Treat the answer as part of your security posture, not as a quarterly cleanup task you&rsquo;ll get to.</p>
<p><em>Questions, sharp disagreements, or a 5.x build that won&rsquo;t compile? Find us on the <a href="https://forums.percona.com/">Percona Forum or, if you&rsquo;re a customer, in your support portal. If you want to become one and ensure your databases run, check out </a><a href="https://www.percona.com/services/">Percona Services</a><a href="https://forums.percona.com/">.</a></em></p>
<p>The post <a href="https://www.percona.com/blog/cve-2026-8053-we-dont-use-time-series-is-not-a-mitigation/">CVE-2026-8053: &ldquo;We don&rsquo;t use time-series&rdquo; is not a mitigation</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/cve-2026-8053-we-dont-use-time-series-is-not-a-mitigation/">CVE-2026-8053: “We don’t use time-series” is not a mitigation</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Not All Open Source Is Equal: Choosing a PostgreSQL Operator for Kubernetes in 2026</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/not-all-open-source-is-equal-choosing-postgresql-operator-kubernetes-2026/" />
      <id>https://www.percona.com/blog/not-all-open-source-is-equal-choosing-postgresql-operator-kubernetes-2026/</id>
      <updated>2026-05-19T17:12:07+00:00</updated>
      <author><name>Slava Sarzhan</name></author>
      <summary type="html"><![CDATA[<p>Choosing an open source PostgreSQL operator for Kubernetes used to be a question about features and community size. In 2026, it has become a question about licensing posture, image distribution, and whether the project you pick today will still be operationally open in three years. This is part 1 of a 3-part series on running … Continued<br />
The post Not All Open Source Is Equal: Choosing a PostgreSQL Operator for Kubernetes in 2026 appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/not-all-open-source-is-equal-choosing-postgresql-operator-kubernetes-2026/">Not All Open Source Is Equal: Choosing a PostgreSQL Operator for Kubernetes in 2026</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400"><img decoding="async" loading="lazy" class="aligncenter wp-image-47116 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/hero-2-1024x375.png" alt="" width="1024" height="375"><br>
Choosing an open source PostgreSQL operator for Kubernetes used to be a question about features and community size. In 2026, it has become a question about licensing posture, image distribution, and whether the project you pick today will still be operationally open in three years.</span></p>
<p>This is <b>part 1 of a 3-part series</b><span style="font-weight: 400"> on running PostgreSQL on Kubernetes with a fully open-source operator.</span></p>
<ul>
<li style="font-weight: 400"><b>Part 1 (this post)</b><span style="font-weight: 400">: how the open-source landscape has shifted under your feet, and what to look for in an operator before you commit</span></li>
<li style="font-weight: 400"><b>Part 2</b><span style="font-weight: 400">: migrating from the Crunchy Data PostgreSQL Operator to the Percona PostgreSQL Operator using the standby cluster method (near-zero downtime)</span></li>
<li style="font-weight: 400"><b>Part 3</b><span style="font-weight: 400">: two simpler migration paths: backup-and-restore and persistent-volume reuse</span></li>
</ul>
<p><span style="font-weight: 400">In this post, you will learn about:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">What has changed in the open-source landscape over the last few years, with specific examples</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">What licensing and redistribution actually mean for Kubernetes operators in production</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">How to evaluate whether a project is &ldquo;open source in theory&rdquo; or open source in practice</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Where Percona&rsquo;s PostgreSQL Operator fits in, and what the practical migration looks like</span></li>
</ul>
<p><b>Open source isn&rsquo;t what it used to be</b><b><br>
</b><span style="font-weight: 400">The landscape of open source has undergone significant changes in recent years, and selecting the right operator and tooling for PostgreSQL clusters in Kubernetes has never been more important. Three recent shifts illustrate the pattern.</span></p>
<p>&nbsp;</p>
<h3><b><br>
MinIO</b><a class="anchor-link" id="minio"></a></h3>
<p><span style="font-weight: 400">MinIO was the default open-source S3-compatible storage backend for Kubernetes workloads for years. The trajectory over the last few years tells the story:</span></p>
<ul>
<li style="font-weight: 400"><a href="https://blog.min.io/agplv3-open-source-license/"><span style="font-weight: 400">Switched its license to AGPLv3</span></a><span style="font-weight: 400">, with several enterprise features moved into a commercial-only edition</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Entered what amounted to maintenance mode, narrowing community engagement, limiting support to paid subscriptions, and reducing acceptance of community contributions</span></li>
<li style="font-weight: 400"><b>On April 25, 2026, the </b><a href="https://github.com/minio/minio"><b>github.com/minio/minio</b></a><b> repository was archived by the project owner</b><span style="font-weight: 400">, ending public development of the open-source version</span></li>
</ul>
<p><span style="font-weight: 400">The code is still cloneable, but the project is no longer maintained as open source. Teams running MinIO in production now need an exit plan.</span></p>
<p>&nbsp;</p>
<h3><b>Bitnami images</b><a class="anchor-link" id="bitnami-images"></a></h3>
<p><span style="font-weight: 400">Bitnami Docker images have long been a staple for databases (including Postgres), middleware, and developer tooling. In July 2025, Broadcom&rsquo;s Tanzu Division </span><a href="https://news.broadcom.com/app-dev/broadcom-introduces-bitnami-secure-images-for-production-ready-containerized-applications"><span style="font-weight: 400">announced Bitnami Secure Images</span></a><span style="font-weight: 400"> and </span><a href="https://github.com/bitnami/containers/issues/83267"><span style="font-weight: 400">signalled the deprecation of the free public catalog</span></a><span style="font-weight: 400">. The concrete timeline that followed:</span></p>
<ul>
<li style="font-weight: 400"><b>August 28, 2025</b><span style="font-weight: 400">: deprecation of non-hardened Debian-based images in the free tier began, and non-latest images started to be removed</span></li>
<li style="font-weight: 400"><b>September 29, 2025</b><span style="font-weight: 400"> (after community pushback): the public </span><span style="font-weight: 400">docker.io/bitnami</span><span style="font-weight: 400"> catalog was reduced. The remaining free images were limited to a small curated set of latest-version, hardened images intended for development use; older versions of most applications were moved to a &ldquo;Bitnami Legacy&rdquo; repository</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The full catalog and the hardened production images now require a paid</span><b> Bitnami Secure Images</b><span style="font-weight: 400"> subscription, </span><a href="https://thenewstack.io/broadcom-ends-free-bitnami-images-forcing-users-to-find-alternatives/"><span style="font-weight: 400">reportedly priced in the tens of thousands of dollars per year</span></a><span style="font-weight: 400"> per organization</span></li>
</ul>
<p><span style="font-weight: 400">For Kubernetes teams, the practical impact was immediate: any Helm chart that pinned a specific Bitnami image version (a recommended practice) found that image gone or moved, breaking CI pipelines and air-gapped deployments.</span></p>
<p>&nbsp;</p>
<h3><b>Crunchy Data PostgreSQL images</b><a class="anchor-link" id="crunchy-data-postgresql-images"></a></h3>
<p><span style="font-weight: 400">Crunchy Data <a href="https://www.percona.com/blog/support-for-crunchy-hardened-postgresql-ends-soon-dont-get-caught-off-guard/">illustrates the same dynamic in the Postgres operator space</a>. To be clear: the Crunchy Data PostgreSQL Operator is a mature, well-engineered project, and the team behind it has done a lot of valuable work upstream and around pgBackRest and Patroni integrations. The point of this section is not the engineering, it is the redistribution and usage terms that govern the official builds.</span></p>
<h3>Crunchy&rsquo;s licensing shifts, 2022 to 2024<a class="anchor-link" id="crunchys-licensing-shifts-2022-to-2024"></a></h3>
<p>Between 2022 and 2024, several shifts occurred:</p>
<ul>
<li style="font-weight: 400"><b>Redistribution restrictions.</b><span style="font-weight: 400"> While the PostgreSQL code is open source, Crunchy&rsquo;s official Docker images include branding and enterprise features that are not freely redistributable. The </span><a href="https://www.crunchydata.com/developers/terms"><span style="font-weight: 400">Crunchy Data Developer Program terms</span></a><span style="font-weight: 400"> describe the software as intended for internal or personal use; production use by larger organizations typically requires an active support subscription.</span></li>
<li style="font-weight: 400"><b>Restrictions on consulting and resale.</b><span style="font-weight: 400"> The terms explicitly prohibit using Crunchy&rsquo;s images to deliver support or consulting services to others without an authorized agreement. The PostgreSQL source code remains open source, but the official images and their packaging are not freely redistributable, which limits practical use in commercial and customer-facing settings.</span></li>
<li style="font-weight: 400"><b>Registry move.</b><span style="font-weight: 400"> Most images were moved to </span><span style="font-weight: 400">registry.developers.crunchydata.com</span><span style="font-weight: 400">, which requires authentication and acceptance of terms before pulling. That draws a clearer line between open-source code and proprietary builds.</span></li>
</ul>
<p><span style="font-weight: 400">In other words, the project is open source on the code side, but the practical artifacts (images, Helm releases) are gated.</span></p>
<p>&nbsp;</p>
<h3><b>What these restrictions really mean for Kubernetes users</b><a class="anchor-link" id="what-these-restrictions-really-mean-for-kubernetes-users"></a></h3>
<p><span style="font-weight: 400">When container images and operators come with redistribution limits, authentication requirements, or &ldquo;internal-use-only&rdquo; clauses, the impact on Kubernetes environments is immediate and concrete. Teams can no longer:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Build air-gapped clusters by mirroring images to a private registry without working through a license review</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Rely on GitOps workflows that assume publicly accessible OCI images</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Fork or customize the operator freely, because official images cannot be redistributed with modifications</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Use the software in commercial or customer-facing products without additional licensing</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Run multi-cluster or multi-tenant Postgres at scale without bumping into usage terms</span></li>
</ul>
<p><span style="font-weight: 400">For a database operator, where almost every operational pattern depends on the container images you can pull and run, these restrictions effectively turn a project into a &ldquo;source-available but not operationally open&rdquo; solution. The code is open. The operating story is not.</span><br>
<span style="font-weight: 400">As a result, many teams are switching to fully open-source alternatives: the <a href="https://docs.percona.com/percona-operator-for-postgresql/latest/">Percona Operator for PostgreSQL</a>, <a href="https://cloudnative-pg.io/">CloudNativePG</a>, <a href="https://github.com/zalando/postgres-operator">Zalando Postgres Operator</a>, <a href="https://stackgres.io/">StackGres</a>, and a few others.</span></p>
<p>&nbsp;</p>
<h3><b>How to evaluate &ldquo;open source&rdquo; in 2026</b><a class="anchor-link" id="how-to-evaluate-open-source-in-2026"></a></h3>
<p><span style="font-weight: 400">The bigger picture here is that &ldquo;open source&rdquo; today often exists more in theory than in practice. It pays to look past the badge and check the operating reality. Three questions to ask before you commit to an operator:</span></p>
<h4>1. Are the container images publicly redistributable?</h4>
<p><span style="font-weight: 400">If you cannot pull the official images without authentication, or you cannot mirror them to your private registry without a license review, your air-gapped and GitOps stories are constrained from day one. This is the question that turned out to be the most consequential one for MinIO, Bitnami, and Crunchy users in 2025.<br>
</span></p>
<h4>2. Are core operational features in the open-source build, or behind a paywall?</h4>
<p>Backup, monitoring, HA, and security features should be in the build everyone uses, not gated behind an enterprise tier. A &ldquo;community edition&rdquo; that omits the feature most teams actually need is a marketing build, not a real open-source build.</p>
<h4>3. Is the governance and roadmap public?</h4>
<p>A project where you can see the issues, the PRs, and the roadmap is one you can plan around. The Percona PG Operator&rsquo;s&nbsp;<a href="https://github.com/orgs/percona/projects/10/views/6">public roadmap</a>&nbsp;is an example of what this looks like in practice. A project run inside a vendor&rsquo;s private tracker, by contrast, gives you no visibility.<br>
These are not gotchas. They are the questions that decide whether a project will still serve you the same way in three years.</p>
<p>&nbsp;</p>
<h3><b><br>
Migrate to freedom</b><a class="anchor-link" id="migrate-to-freedom"></a></h3>
<h4>Announcing the hard fork</h4>
<p><span style="font-weight: 400">We strongly believe in fully open-source software and want to increase our investment in the PostgreSQL and Kubernetes ecosystems. To back that up, we have decided to </span><b>hard fork the Crunchy Data PostgreSQL Kubernetes Operator</b><span style="font-weight: 400">. Starting from version <strong>3.0.0 (coming soon)</strong>, the Percona PostgreSQL Kubernetes Operator is a <strong>fully independent</strong> project, with a </span><a href="https://github.com/orgs/percona/projects/10/views/6"><span style="font-weight: 400">public roadmap</span></a><span style="font-weight: 400">, public issue tracker, and freely redistributable images.<br>
</span><br>
<span style="font-weight: 400">The hard fork is not a critique of Crunchy&rsquo;s engineering. It is a commitment that the operator will keep evolving in a fully open-source direction, with no surprises about which features will be available to which audience.</span></p>
<h4>Why migration is straightforward</h4>
<p>Because the Percona PostgreSQL Operator is a hard fork of the Crunchy operator, the migration paths are surprisingly straightforward. The same underlying tools (Patroni, pgBackRest, PgBouncer) and the same overall design are used in both, which means migration can be done in multiple ways, sometimes with near-zero downtime, sometimes faster with a small downtime window. The next two posts in this series walk through three concrete options.</p>
<h3><b>What&rsquo;s next</b><a class="anchor-link" id="whats-next"></a></h3>
<p><span style="font-weight: 400">This was the &ldquo;why.&rdquo; The next two posts are the &ldquo;how&rdquo;:</span></p>
<ul>
<li style="font-weight: 400"><b>Part 2: Standby cluster migration.</b><span style="font-weight: 400"> Bring up a Percona cluster as a standby of the Crunchy primary, catch it up via pgBackRest plus streaming replication, and promote it at cutover. The only downtime is the cutover itself.</span></li>
<li style="font-weight: 400"><b>Part 3: Backup-restore and PV reuse.</b><span style="font-weight: 400"> Two simpler paths: bootstrap a Percona cluster directly from a Crunchy pgBackRest backup, or retain the existing PGDATA persistent volume and have Percona pick up where Crunchy left off.</span></li>
</ul>
<h4>Reversibility and exit options</h4>
<p>All three paths are reversible: because Percona&rsquo;s operator, images, and tooling are 100 percent open source and remain compatible with the same backup format and the same Patroni HA model, you keep full control. You can migrate back to Crunchy if your team decides to, or out to another open-source operator (CloudNativePG, Zalando, StackGres) using the same patterns. That last journey is a topic for a future article.</p>
<p><span style="font-weight: 400">This series covers basic deployment patterns and simplified configuration examples. If your environment is more complex, uses custom images, includes Crunchy enterprise features like TDE, or otherwise needs tailored migration steps, contact the Percona team and we will help you plan and execute the move.</span></p>
<p>&nbsp;</p>
<h3><b><br>
Try It Out</b><a class="anchor-link" id="try-it-out"></a></h3>
<ul>
<li style="font-weight: 400"><b>Percona Operator for PostgreSQL docs:</b><a href="https://docs.percona.com/percona-operator-for-postgresql/latest/"> https://docs.percona.com/percona-operator-for-postgresql/latest/</a></li>
<li style="font-weight: 400"><b>GitHub:</b> <a href="https://github.com/percona/percona-postgresql-operator"><span style="font-weight: 400">https://github.com/percona/percona-postgresql-operator</span></a></li>
<li style="font-weight: 400"><b>Public roadmap:</b> <a href="https://github.com/orgs/percona/projects/10/views/6"><span style="font-weight: 400">https://github.com/orgs/percona/projects/10/views/6</span></a></li>
<li style="font-weight: 400"><b>Issue tracker: </b><a href="https://github.com/percona/percona-postgresql-operator/issues"><span style="font-weight: 400">https://github.com/percona/percona-postgresql-operator/issues</span></a></li>
<li style="font-weight: 400"><b>Community Forum: </b><a href="https://forums.percona.com/c/postgresql/percona-kubernetes-operator-for-postgresql/68">https://forums.percona.com/c/postgresql/percona-kubernetes-operator-for-postgresql/68</a></li>
</ul>
<p>The post <a href="https://www.percona.com/blog/not-all-open-source-is-equal-choosing-postgresql-operator-kubernetes-2026/">Not All Open Source Is Equal: Choosing a PostgreSQL Operator for Kubernetes in 2026</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/not-all-open-source-is-equal-choosing-postgresql-operator-kubernetes-2026/">Not All Open Source Is Equal: Choosing a PostgreSQL Operator for Kubernetes in 2026</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Backrest&#8217;s back, alright!</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/05/19/backrests-back-alright/" />
      <id>https://percona.community/blog/2026/05/19/backrests-back-alright/</id>
      <updated>2026-05-19T11:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>Events unfolded quickly over the course of a couple of weeks starting on 27 April 2026, when a message appeared on the pgBackRest project announcing: that the repository would be archived and active maintenance would stop.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/19/backrests-back-alright/">Backrest&#8217;s back, alright!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Events unfolded quickly over the course of a couple of weeks starting on 27 April 2026, when a <a href="https://pgbackrest.org/news.html" target="_blank" rel="noopener noreferrer">message appeared on the pgBackRest project announcing</a>:<br>
that the repository would be archived and active maintenance would stop.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/Jan-pgb-news-1.png" alt="blog/2026/05/Jan-pgb-news-1.png"></figure>
</p>
<p>For many in the PostgreSQL ecosystem, this landed like a shock. <a href="https://pgbackrest.org/" target="_blank" rel="noopener noreferrer">pgBackRest</a> is one of the most widely used backup and recovery tools for PostgreSQL, deeply embedded in production environments across enterprises large and small. Now it was suddenly described as &ldquo;<a href="https://mydbanotebook.org/posts/pgbackrest-is-dead.-now-what/" target="_blank" rel="noopener noreferrer">dead</a>&rdquo;, &ldquo;<a href="https://www.gabrielebartolini.it/articles/2026/04/why-the-cycle-of-open-source-sustainability-needs-to-be-virtuous/" target="_blank" rel="noopener noreferrer">EOL</a>&rdquo;, or &ldquo;<a href="https://news.ycombinator.com/item?id=47919997" target="_blank" rel="noopener noreferrer">abandoned</a>&rdquo;. The trigger was clear: its long-time maintainer, after more than a decade of work, announced he could no longer continue without sustainable funding and would archive the repository.<br>
i<br>
That message spread fast. The interpretation spread even faster.</p>
<p>And it was wrong.</p>
<h2 id="this-wasnt-eol">This wasn&rsquo;t EOL<a class="anchor-link" id="this-wasnt-eol"></a></h2>
<p>Open source software doesn&rsquo;t simply &ldquo;go end of life&rdquo; in the way proprietary software does. There is no vendor switch flipped to OFF. No license revoked. No binaries disappearing overnight.</p>
<p>What actually happens is more subtle and more important:</p>
<ul>
<li>Maintainers step away</li>
<li>Funding runs out</li>
<li>Work stops</li>
</ul>
<p>That&rsquo;s not EOL. That&rsquo;s a sustainability gap.</p>
<p><a href="https://github.com/pgbackrest/pgbackrest" target="_blank" rel="noopener noreferrer">pgBackRest</a> didn&rsquo;t die. It hit a problem seen too often in open source world: a critical piece of infrastructure maintained by fewer and fewer people, until it ultimately depended on one person being able to justify working on it full time.</p>
<h2 id="the-real-problem">The real problem<a class="anchor-link" id="the-real-problem"></a></h2>
<p>The message from the maintainer was not about abandoning the project. It was about reality:</p>
<blockquote>
<p>maintaining a widely used tool requires time, and time requires funding</p>
</blockquote>
<p>For years, pgBackRest was supported through corporate sponsorship from mainly one vendor. When that disappeared due to the Crunchy Data acquisition, so did the ability to keep investing the same level of effort.</p>
<p>This is the &ldquo;<a href="https://xkcd.com/2347/" target="_blank" rel="noopener noreferrer">Nebraska guy problem</a>&rdquo; in action: software used by a large part of the industry, sustained by a very small number of people.</p>
<p>Yes, anyone can fork the project (and some already did), but:</p>
<ul>
<li>trust doesn&rsquo;t fork</li>
<li>community doesn&rsquo;t fork</li>
<li>sustainability definitely doesn&rsquo;t fork</li>
</ul>
<p>A fork without coordination creates fragmentation without adding real value and that weakens the ecosystem. What pgBackRest needed was not a replacement, but continuity.</p>
<h2 id="the-danger-of-bad-framing">The danger of bad framing<a class="anchor-link" id="the-danger-of-bad-framing"></a></h2>
<p>Calling the project &ldquo;dead&rdquo; shifted the conversation in the wrong direction.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/Jan-pgb-not-dead.png" alt="blog/2026/05/Jan-pgb-not-dead.png"></figure>
</p>
<p>Instead of asking:</p>
<blockquote>
<p>how do we keep this project healthy?</p>
</blockquote>
<p>the discussion drifted at best toward:</p>
<blockquote>
<p>what is the strategic solution here?</p>
</blockquote>
<p>and more often to:</p>
<blockquote>
<p>what do we replace it with?</p>
</blockquote>
<p>and</p>
<blockquote>
<p>what do we name our fork?</p>
</blockquote>
<p>That&rsquo;s a natural reaction, but it&rsquo;s not a good one.</p>
<p>Critical infrastructure should not be treated as disposable. Doing so erodes trust in the solutions we rely on and weakens the ecosystem. These foundational pieces should be treated as a shared responsibility so that the entire community becomes stronger.</p>
<h2 id="what-happened-next">What happened next<a class="anchor-link" id="what-happened-next"></a></h2>
<p>Behind the scenes, things moved quickly, with coordination between David and companies active in the PostgreSQL community.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/Jan-pgb-news-2.png" alt="blog/2026/05/Jan-pgb-news-2.png"></figure>
</p>
<p>Conversations started across companies, contributors and the wider ecosystem. The goal wasn&rsquo;t to &ldquo;rescue&rdquo; pgBackRest, but to do something far more valuable: to restore a sustainable model around it.</p>
<p>This is what open source actually requires: not heroics, but coordination.</p>
<h2 id="so-whats-with-pgbackrest">So what&rsquo;s with pgBackRest?<a class="anchor-link" id="so-whats-with-pgbackrest"></a></h2>
<p>It&rsquo;s all good. Well, better.</p>

<p>The short version:</p>
<ul>
<li><a href="https://pgbackrest.org/news.html#will-continue" target="_blank" rel="noopener noreferrer">Multiple companies coordinated together</a> to <a href="https://www.globenewswire.com/news-release/2026/05/19/3297383/0/en/open-source-stays-open-percona-sponsors-pgbackrest-to-keep-postgresql-backups-running.html" target="_blank" rel="noopener noreferrer">ensure continued funding and support around pgBackRest</a></li>
<li>Engineering effort is now being shared more broadly to expand the contributor and maintainer base</li>
<li>Discussions around longer term sustainability and governance in the PostgreSQL ecosystem accelerated significantly</li>
<li><strong>Percona</strong> played an active role in coordinating these efforts, contributing engineering resources, and helping bring organizations together around a sustainable path forward</li>
</ul>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/Jan-pgb-news-3.png" alt="blog/2026/05/Jan-pgb-news-3.png"></figure>
</p>
<p>The project was never closed.</p>
<h2 id="the-way-forward-is-open">The way (forward) is open<a class="anchor-link" id="the-way-forward-is-open"></a></h2>
<p>pgBackRest&rsquo;s situation is not unique. It&rsquo;s a signal.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/Jan-pgb-back.png" alt="blog/2026/05/Jan-pgb-back.png"></figure>
</p>
<p>The PostgreSQL ecosystem depends on a wide range of tools that don&rsquo;t have the same visibility, or funding, as the database itself. That gap is becoming harder to ignore.</p>
<p>There&rsquo;s growing alignment on a few things:</p>
<ul>
<li>sustainability needs to be intentional</li>
<li>funding needs to be easier to organize</li>
<li>engineering effort needs to be shared</li>
</ul>
<p>Whether that leads to an umbrella foundation or another model, one thing is clear: the ecosystem needs structures that support both users and maintainers.</p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/19/backrests-back-alright/">Backrest&#8217;s back, alright!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Community Server Q2 2026 maintenance releases</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/mariadb-community-server-q2-2026-maintenance-releases/" />
      <id>https://mariadb.com/resources/blog/mariadb-community-server-q2-2026-maintenance-releases/</id>
      <updated>2026-05-18T21:10:41+00:00</updated>
      <author><name>Daniel Bartholomew</name></author>
      <summary type="html"><![CDATA[<p>MariaDB is pleased to announce the immediate availability of MariaDB Community Server 11.8.7, 11.4.11, 10.11.17, and 10.6.26 maintenance releases. See […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-community-server-q2-2026-maintenance-releases/">MariaDB Community Server Q2 2026 maintenance releases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB is pleased to announce the immediate availability of MariaDB Community Server 11.8.7, 11.4.11, 10.11.17, and 10.6.26 maintenance releases. See the release notes and changelogs for additional details on each release and visit mariadb.com/downloads to download.</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-community-server-q2-2026-maintenance-releases/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-community-server-q2-2026-maintenance-releases/">MariaDB Community Server Q2 2026 maintenance releases</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Introducing Our First MariaDB Server Solution Stack: A Privacy-First Stack with Nextcloud, Passbolt, and MariaDB</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/introducing-our-first-mariadb-server-solution-stack-a-privacy-first-stack-with-nextcloud-passbolt-and-mariadb/" />
      <id>https://mariadb.org/introducing-our-first-mariadb-server-solution-stack-a-privacy-first-stack-with-nextcloud-passbolt-and-mariadb/</id>
      <updated>2026-05-15T14:27:49+00:00</updated>
      <author><name>Anna Widenius</name></author>
      <summary type="html"><![CDATA[<p>MariaDB Foundation is pleased to announce the publication of our first MariaDB Server Solution Stack in the MariaDB Server Ecosystem Hub:<br />
Privacy-First Stack: Nextcloud, Passbolt, and MariaDB Server<br />
This stack brings together three open-source technologies with a shared purpose: helping organizations build collaboration infrastructure around privacy, control, and long-term digital sovereignty. …<br />
Continue reading \"Introducing Our First MariaDB Server Solution Stack: A Privacy-First Stack with Nextcloud, Passbolt, and MariaDB\"<br />
The post Introducing Our First MariaDB Server Solution Stack: A Privacy-First Stack with Nextcloud, Passbolt, and MariaDB appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/introducing-our-first-mariadb-server-solution-stack-a-privacy-first-stack-with-nextcloud-passbolt-and-mariadb/">Introducing Our First MariaDB Server Solution Stack: A Privacy-First Stack with Nextcloud, Passbolt, and MariaDB</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB Foundation is pleased to announce the publication of our first MariaDB Server Solution Stack in the MariaDB Server Ecosystem Hub:<br>
<a href="https://ecohub.mariadb.org/stacks/privacy-first-stack?utm_source=chatgpt.com">Privacy-First Stack: Nextcloud, Passbolt, and MariaDB Server</a><br>
This stack brings together three open-source technologies with a shared purpose: helping organizations build collaboration infrastructure around privacy, control, and long-term digital sovereignty. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/introducing-our-first-mariadb-server-solution-stack-a-privacy-first-stack-with-nextcloud-passbolt-and-mariadb/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Introducing Our First MariaDB Server Solution Stack: A Privacy-First Stack with Nextcloud, Passbolt, and MariaDB&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/introducing-our-first-mariadb-server-solution-stack-a-privacy-first-stack-with-nextcloud-passbolt-and-mariadb/">Introducing Our First MariaDB Server Solution Stack: A Privacy-First Stack with Nextcloud, Passbolt, and MariaDB</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/introducing-our-first-mariadb-server-solution-stack-a-privacy-first-stack-with-nextcloud-passbolt-and-mariadb/">Introducing Our First MariaDB Server Solution Stack: A Privacy-First Stack with Nextcloud, Passbolt, and MariaDB</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Community Server 10.6 Is Reaching End of Life – Here’s What to Do Next</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/mariadb-community-server-10-6-is-reaching-end-of-life-heres-what-to-do-next/" />
      <id>https://mariadb.com/resources/blog/mariadb-community-server-10-6-is-reaching-end-of-life-heres-what-to-do-next/</id>
      <updated>2026-05-14T17:21:52+00:00</updated>
      <author><name>Ralf Gebhardt</name></author>
      <summary type="html"><![CDATA[<p>If you’re running MariaDB Community Server 10.6, mark your calendar: July 6, 2026 is the official End of Life (EOL) […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-community-server-10-6-is-reaching-end-of-life-heres-what-to-do-next/">MariaDB Community Server 10.6 Is Reaching End of Life – Here’s What to Do Next</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>If you&rsquo;re running MariaDB Community Server 10.6, mark your calendar: July 6, 2026 is the official End of Life (EOL) date. After that date, there will no longer be security patches, bug fixes, or updates for this version. That&rsquo;s not a distant concern &ndash; it&rsquo;s a few weeks away. And if you haven&rsquo;t started planning your next move, now is the time. This post walks you through what EOL actually&hellip;</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-community-server-10-6-is-reaching-end-of-life-heres-what-to-do-next/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/mariadb-community-server-10-6-is-reaching-end-of-life-heres-what-to-do-next/">MariaDB Community Server 10.6 Is Reaching End of Life – Here’s What to Do Next</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Documented: The MariaDB Server (Community) Contribution Process</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/documented-the-mariadb-server-community-contribution-process/" />
      <id>https://mariadb.org/documented-the-mariadb-server-community-contribution-process/</id>
      <updated>2026-05-11T14:13:34+00:00</updated>
      <author><name>Georgi Kodinov</name></author>
      <summary type="html"><![CDATA[<p>If you ever considered contributing code to the MariaDB server, you should know that this is an intricate process involving multiple steps and multiple actors. To help you see your contributions successfully merged into the MariaDB Server codebase I’ve compiled a comprehensive description of the contribution process itself, the roles involved into it, the sequence of actions and conditions for transition from one to another. …<br />
Continue reading \"Documented: The MariaDB Server (Community) Contribution Process\"<br />
The post Documented: The MariaDB Server (Community) Contribution Process appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/documented-the-mariadb-server-community-contribution-process/">Documented: The MariaDB Server (Community) Contribution Process</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>If you ever considered contributing code to the MariaDB server, you should know that this is an intricate process involving multiple steps and multiple actors. To help you see your contributions successfully merged into the MariaDB Server <a href="https://github.com/MariaDB/server/">codebase</a> I&rsquo;ve compiled a comprehensive description of the contribution process itself, the roles involved into it, the sequence of actions and conditions for transition from one to another. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/documented-the-mariadb-server-community-contribution-process/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Documented: The MariaDB Server (Community) Contribution Process&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/documented-the-mariadb-server-community-contribution-process/">Documented: The MariaDB Server (Community) Contribution Process</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/documented-the-mariadb-server-community-contribution-process/">Documented: The MariaDB Server (Community) Contribution Process</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Our Experience at MongoDB.local London 2026: The Era of AI Agents, Badges, and&#8230; Surviving on Chips!</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/05/11/our-experience-at-mongodb.local-london-2026-the-era-of-ai-agents-badges-and...-surviving-on-chips/" />
      <id>https://percona.community/blog/2026/05/11/our-experience-at-mongodb.local-london-2026-the-era-of-ai-agents-badges-and...-surviving-on-chips/</id>
      <updated>2026-05-11T00:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>On May 7th, Keith (Quality Engineer, Percona for MongoDB) and I had the super cool opportunity to head over to MongoDB.local London! The event was amazing and packed with insights about where the database ecosystem is heading.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/11/our-experience-at-mongodb.local-london-2026-the-era-of-ai-agents-badges-and...-surviving-on-chips/">Our Experience at MongoDB.local London 2026: The Era of AI Agents, Badges, and&#8230; Surviving on Chips!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>On May 7th, <strong>Keith</strong> (Quality Engineer, Percona for MongoDB) and I had the super cool opportunity to head over to <a href="https://www.mongodb.com/events/mongodb-local/london" target="_blank" rel="noopener noreferrer">MongoDB.local London</a>! The event was amazing and packed with insights about where the database ecosystem is heading.</p>
<p>If there was one massive takeaway from the day, it was this: <strong>We are officially in the Era of AI and &ldquo;Agentic&rdquo; Systems.</strong> During the event, the message was clear: we are shifting from basic LLMs (that just answer a prompt and forget it) to autonomous AI Agents that follow a continuous loop of Perception &rarr; Planning &rarr; Action. MongoDB&rsquo;s President and CEO, CJ Desai, repeated a powerful phrase:</p>
<blockquote>
<p>While AI models change rapidly, the Data Layer is the constant.</p>
</blockquote>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-desai.png" alt="ceo"></figure>
</p>
<p>Here is a look at our day, what we learned, and the fun we had along the way!</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-team.png" alt="team"></figure>
</p>
<h3 id="arriving-early-and-chasing-badges">Arriving Early and Chasing Badges<a class="anchor-link" id="arriving-early-and-chasing-badges"></a></h3>
<p>We got a great tip before the event: arrive early to get a head start on the gamified learning! MongoDB had a super nice setup where you could take tests on Credly to earn knowledge badges.</p>
<p>We jumped right in. I got a <strong>MongoDB Overview badge</strong>, but Keith was on a mission. He completed three different tests (including MongoDB for Developers) and unlocked some cool swag: a really cute, high-quality bag! It was a brilliant way to get attendees engaged right from the morning.</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-skills.png" alt="skills"></figure>
</p>
<p><a href="https://www.credly.com/organizations/mongodb/collections/mongodb-skill-badges/badge_templates" target="_blank" rel="noopener noreferrer">Here</a> are more badges in case you want to get yours!</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-badges.png" alt="badges"></figure>
</p>
<h3 id="general-session-highlights">General Session Highlights<a class="anchor-link" id="general-session-highlights"></a></h3>
<p>We spent a lot of our time in the main room for the General Session, and the announcements were packed with impressive numbers and tech:</p>
<ul>
<li><strong>MongoDB 8.3 is Fast:</strong> Osmar Olivo (Senior Director, Database Product Management) shared that the new version brings up to 35% more write throughput, 45% more read throughput, and 15% more for ACID transactions.</li>
<li><strong>The Scale is Real:</strong> We learned that Stripe uses MongoDB to process over $1 trillion in payments volume every year (maintaining 5 nines of availability!). Osmar framed this perfectly: that is 1.5% of the global GDP running through MongoDB.</li>
<li><strong>LangGraph.js Store Integration:</strong> This was a big one for developers. MongoDB is positioning itself as the &ldquo;memory hard drive&rdquo; for AI agents. By supporting JavaScript and TypeScript, they are making it super easy for companies to use their existing web developers to build complex AI workflows.</li>
<li><strong>Hugging Face Partnership:</strong> They are scaling with MongoDB Atlas to support over 3 million models, officially tying themselves to the &ldquo;GitHub for AI.&rdquo;</li>
</ul>
<p>Feel free to explore the recorded sessions for more: <a href="https://www.youtube.com/watch?v=mHOQWeuoreM&amp;t=1877s" target="_blank" rel="noopener noreferrer">MongoDB.local London 2026</a></p>
<h3 id="guest-speakers">Guest Speakers<a class="anchor-link" id="guest-speakers"></a></h3>
<p><strong>Ulku Rowe</strong> (CIO, Commercial Business at Lloyds Banking Group) talked about this being the &ldquo;Decade of AI.&rdquo; Lloyds is actively upskilling their current engineers through an internal &ldquo;AI Academy&rdquo; built in partnership with Cambridge University! She emphasized that as they build out this infrastructure, partnerships are absolutely critical to their success.</p>
<p>We also heard from <strong>Alex Holt</strong> from ElevenLabs, a company focused on producing the absolute best, human-sounding voice AI. Their scale is wild: they have 40 million agents running and hit $500 million in Annual Recurring Revenue in just 3 years! Alex mentioned that because many enterprises don&rsquo;t know how to build agents yet, ElevenLabs uses &ldquo;forward deployed engineers&rdquo; to sit directly with customers to build, deploy, and prove the ROI of their voice agents.</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-eleven.png" alt="eleven"></figure>
</p>
<h3 id="the-hands-on-workshop-and-our-lunch-diet">The Hands-on Workshop and Our Lunch &ldquo;Diet&rdquo;<a class="anchor-link" id="the-hands-on-workshop-and-our-lunch-diet"></a></h3>
<p>Later in the day, we attended a hands-on workshop: <strong>Designing Memory Systems for AI Agents</strong>, hands-on workshop about how AI agents can remember information and use it later to give better responses. We used Python and MongoDB Atlas to build memory into an AI agent and learned how to store, search, update, and manage that memory.<br>
The setup was good, everything was prepared in advance so we could focus on executing the commands and truly understanding the concepts. At the end, we answered some questions and earned another badge!</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-workshop.png" alt="badges"></figure>
</p>
<p>However, the workshop ran until 1:00 PM. One of our friends had warned us to &ldquo;go for food fast,&rdquo; but we were too focused on the workshop! By the time we made it to the lunch area, all the main food was completely sold out.</p>
<p><strong>How did we survive?</strong> Chips, candies, and a lot of beverages. Between the sodas, coffee, and tea, we kept our energy, but it was definitely a funny learning experience for next time! I can imagine Keith arriving home for dinner!!</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-gif.gif" alt="badges"></figure>
</p>
<h3 id="exploring-the-sponsor-hall-and-doing-a-podcast">Exploring the Sponsor Hall (And Doing a Podcast!)<a class="anchor-link" id="exploring-the-sponsor-hall-and-doing-a-podcast"></a></h3>
<p>We spent our afternoon speaking with sponsors and even got to participate in a quick podcast focusing on AI and how Atlas is being used as a strong platform for these projects!</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-podcast.png" alt="podcast"></figure>
</p>
<p>The person being interviewed was <strong>Bikram Das</strong>, who is Chief Data Architect at Tata Consulting Services, and we had a great chat with him. TCS and MongoDB have partnered on a super impressive real-time payment and fraud detection platform. They use autonomous AI agents to instantly assess risk, investigate anomalies, and route safe transactions to networks like Visa and SWIFT without any downtime.</p>
<p>We also talked with IBM folks; they showed us their &ldquo;plug-and-play&rdquo; enterprise AI foundation. They are focused on letting large companies safely deploy AI agents without having to completely rip out and rebuild their current data infrastructure.</p>
<p>We also stopped by the Accenture booth! They are actively working on integrating AI directly into their platforms so they can offer smarter, more advanced solutions to their customers.</p>
<h3 id="wrapping-up">Wrapping Up!<a class="anchor-link" id="wrapping-up"></a></h3>
<p>To cap off a great day, MongoDB had one last treat. If you took less than 3 minutes to fill out the end-of-event survey, they handed you a super nice pair of socks. <em>(We love community ideas like this!)</em>.</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-socks.png" alt="podcast"></figure>
</p>
<p>Overall, <strong>MongoDB.local London</strong> was a great experience. It was a nice space to learn, connect, have hands-on experience, and see exactly how the database world is evolving to meet the Agentic AI era head-on.</p>
<p>See you at the next event!</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-percona.png" alt="bye"></figure></p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/11/our-experience-at-mongodb.local-london-2026-the-era-of-ai-agents-badges-and...-surviving-on-chips/">Our Experience at MongoDB.local London 2026: The Era of AI Agents, Badges, and&#8230; Surviving on Chips!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Our Experience at MongoDB.local London 2026: The Era of AI Agents, Badges, and Surviving on Chips!</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/05/11/our-experience-at-mongodb.local-london-2026-the-era-of-ai-agents-badges-and-surviving-on-chips/" />
      <id>https://percona.community/blog/2026/05/11/our-experience-at-mongodb.local-london-2026-the-era-of-ai-agents-badges-and-surviving-on-chips/</id>
      <updated>2026-05-11T00:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>On May 7th, Keith (Quality Engineer, Percona for MongoDB) and I had the super cool opportunity to head over to MongoDB.local London! The event was amazing and packed with insights about where the database ecosystem is heading.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/11/our-experience-at-mongodb.local-london-2026-the-era-of-ai-agents-badges-and-surviving-on-chips/">Our Experience at MongoDB.local London 2026: The Era of AI Agents, Badges, and Surviving on Chips!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>On May 7th, <strong>Keith</strong> (Quality Engineer, Percona for MongoDB) and I had the super cool opportunity to head over to <a href="https://www.mongodb.com/events/mongodb-local/london" target="_blank" rel="noopener noreferrer">MongoDB.local London</a>! The event was amazing and packed with insights about where the database ecosystem is heading.</p>
<p>If there was one massive takeaway from the day, it was this: <strong>We are officially in the Era of AI and &ldquo;Agentic&rdquo; Systems.</strong> During the event, the message was clear: we are shifting from basic LLMs (that just answer a prompt and forget it) to autonomous AI Agents that follow a continuous loop of Perception &rarr; Planning &rarr; Action. MongoDB&rsquo;s President and CEO, CJ Desai, repeated a powerful phrase:</p>
<blockquote>
<p>While AI models change rapidly, the Data Layer is the constant.</p>
</blockquote>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-desai.png" alt="ceo"></figure>
</p>
<p>Here is a look at our day, what we learned, and the fun we had along the way!</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-team.png" alt="team"></figure>
</p>
<h3 id="arriving-early-and-chasing-badges">Arriving Early and Chasing Badges<a class="anchor-link" id="arriving-early-and-chasing-badges"></a></h3>
<p>We got a great tip before the event: arrive early to get a head start on the gamified learning! MongoDB had a super nice setup where you could take tests on Credly to earn knowledge badges.</p>
<p>We jumped right in. I got a <strong>MongoDB Overview badge</strong>, but Keith was on a mission. He completed three different tests (including MongoDB for Developers) and unlocked some cool swag: a really cute, high-quality bag! It was a brilliant way to get attendees engaged right from the morning.</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-skills.png" alt="skills"></figure>
</p>
<p><a href="https://www.credly.com/organizations/mongodb/collections/mongodb-skill-badges/badge_templates" target="_blank" rel="noopener noreferrer">Here</a> are more badges in case you want to get yours!</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-badges.png" alt="badges"></figure>
</p>
<h3 id="general-session-highlights">General Session Highlights<a class="anchor-link" id="general-session-highlights"></a></h3>
<p>We spent a lot of our time in the main room for the General Session, and the announcements were packed with impressive numbers and tech:</p>
<ul>
<li><strong>MongoDB 8.3 is Fast:</strong> Osmar Olivo (Senior Director, Database Product Management) shared that the new version brings up to 35% more write throughput, 45% more read throughput, and 15% more for ACID transactions.</li>
<li><strong>The Scale is Real:</strong> We learned that Stripe uses MongoDB to process over $1 trillion in payments volume every year (maintaining 5 nines of availability!). Osmar framed this perfectly: that is 1.5% of the global GDP running through MongoDB.</li>
<li><strong>LangGraph.js Store Integration:</strong> This was a big one for developers. MongoDB is positioning itself as the &ldquo;memory hard drive&rdquo; for AI agents. By supporting JavaScript and TypeScript, they are making it super easy for companies to use their existing web developers to build complex AI workflows.</li>
<li><strong>Hugging Face Partnership:</strong> They are scaling with MongoDB Atlas to support over 3 million models, officially tying themselves to the &ldquo;GitHub for AI.&rdquo;</li>
</ul>
<p>Feel free to explore the recorded sessions for more: <a href="https://www.youtube.com/watch?v=mHOQWeuoreM&amp;t=1877s" target="_blank" rel="noopener noreferrer">MongoDB.local London 2026</a></p>
<h3 id="guest-speakers">Guest Speakers<a class="anchor-link" id="guest-speakers"></a></h3>
<p><strong>Ulku Rowe</strong> (CIO, Commercial Business at Lloyds Banking Group) talked about this being the &ldquo;Decade of AI.&rdquo; Lloyds is actively upskilling their current engineers through an internal &ldquo;AI Academy&rdquo; built in partnership with Cambridge University! She emphasized that as they build out this infrastructure, partnerships are absolutely critical to their success.</p>
<p>We also heard from <strong>Alex Holt</strong> from ElevenLabs, a company focused on producing the absolute best, human-sounding voice AI. Their scale is wild: they have 40 million agents running and hit $500 million in Annual Recurring Revenue in just 3 years! Alex mentioned that because many enterprises don&rsquo;t know how to build agents yet, ElevenLabs uses &ldquo;forward deployed engineers&rdquo; to sit directly with customers to build, deploy, and prove the ROI of their voice agents.</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-eleven.png" alt="eleven"></figure>
</p>
<h3 id="the-hands-on-workshop-and-our-lunch-diet">The Hands-on Workshop and Our Lunch &ldquo;Diet&rdquo;<a class="anchor-link" id="the-hands-on-workshop-and-our-lunch-diet"></a></h3>
<p>Later in the day, we attended a hands-on workshop: <strong>Designing Memory Systems for AI Agents</strong>, hands-on workshop about how AI agents can remember information and use it later to give better responses. We used Python and MongoDB Atlas to build memory into an AI agent and learned how to store, search, update, and manage that memory.<br>
The setup was good, everything was prepared in advance so we could focus on executing the commands and truly understanding the concepts. At the end, we answered some questions and earned another badge!</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-workshop.png" alt="badges"></figure>
</p>
<p>However, the workshop ran until 1:00 PM. One of our friends had warned us to &ldquo;go for food fast,&rdquo; but we were too focused on the workshop! By the time we made it to the lunch area, all the main food was completely sold out.</p>
<p><strong>How did we survive?</strong> Chips, candies, and a lot of beverages. Between the sodas, coffee, and tea, we kept our energy, but it was definitely a funny learning experience for next time! I can imagine Keith arriving home for dinner!!</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-gif.gif" alt="badges"></figure>
</p>
<h3 id="exploring-the-sponsor-hall-and-doing-a-podcast">Exploring the Sponsor Hall (And Doing a Podcast!)<a class="anchor-link" id="exploring-the-sponsor-hall-and-doing-a-podcast"></a></h3>
<p>We spent our afternoon speaking with sponsors and even got to participate in a quick podcast focusing on AI and how Atlas is being used as a strong platform for these projects!</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-podcast.png" alt="podcast"></figure>
</p>
<p>The person being interviewed was <strong>Bikram Das</strong>, who is Chief Data Architect at Tata Consulting Services, and we had a great chat with him. TCS and MongoDB have partnered on a super impressive real-time payment and fraud detection platform. They use autonomous AI agents to instantly assess risk, investigate anomalies, and route safe transactions to networks like Visa and SWIFT without any downtime.</p>
<p>We also talked with IBM folks; they showed us their &ldquo;plug-and-play&rdquo; enterprise AI foundation. They are focused on letting large companies safely deploy AI agents without having to completely rip out and rebuild their current data infrastructure.</p>
<p>We also stopped by the Accenture booth! They are actively working on integrating AI directly into their platforms so they can offer smarter, more advanced solutions to their customers.</p>
<h3 id="wrapping-up">Wrapping Up!<a class="anchor-link" id="wrapping-up"></a></h3>
<p>To cap off a great day, MongoDB had one last treat. If you took less than 3 minutes to fill out the end-of-event survey, they handed you a super nice pair of socks. <em>(We love community ideas like this!)</em>.</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-socks.png" alt="podcast"></figure>
</p>
<p>Overall, <strong>MongoDB.local London</strong> was a great experience. It was a nice space to learn, connect, have hands-on experience, and see exactly how the database world is evolving to meet the Agentic AI era head-on.</p>
<p>See you at the next event!</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/mongodb-percona.png" alt="bye"></figure></p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/11/our-experience-at-mongodb.local-london-2026-the-era-of-ai-agents-badges-and-surviving-on-chips/">Our Experience at MongoDB.local London 2026: The Era of AI Agents, Badges, and Surviving on Chips!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Unleashing Innovation Through Plugins</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/unleashing-innovation-through-plugins/" />
      <id>https://mariadb.org/unleashing-innovation-through-plugins/</id>
      <updated>2026-05-08T09:23:58+00:00</updated>
      <author><name>Georgi Kodinov</name></author>
      <summary type="html"><![CDATA[<p>One of the corner stones in MariaDB Foundation’s mission is:<br />
We strive to increase adoption by users and across use cases, platforms and means of deployment. …<br />
Continue reading \"Unleashing Innovation Through Plugins\"<br />
The post Unleashing Innovation Through Plugins appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/unleashing-innovation-through-plugins/">Unleashing Innovation Through Plugins</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>One of the corner stones in MariaDB Foundation&rsquo;s mission is:</p>
<p>We strive to increase adoption by users and across use cases, platforms and means of deployment. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/unleashing-innovation-through-plugins/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Unleashing Innovation Through Plugins&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/unleashing-innovation-through-plugins/">Unleashing Innovation Through Plugins</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/unleashing-innovation-through-plugins/">Unleashing Innovation Through Plugins</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Bringing pt-query-digest-Style Slow Query Analysis to PostgreSQL with pg_enhanced_query_logging</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/bringing-pt-query-digest-style-slow-query-analysis-to-postgresql-with-pg_enhanced_query_logging/" />
      <id>https://www.percona.com/blog/bringing-pt-query-digest-style-slow-query-analysis-to-postgresql-with-pg_enhanced_query_logging/</id>
      <updated>2026-05-08T01:52:10+00:00</updated>
      <author><name>Agustín</name></author>
      <summary type="html"><![CDATA[<p>In this blog post, we are going to briefly discuss pg_enhanced_query_logging (PEQL for short), a PostgreSQL extension that produces slow query logs in the same format MySQL and Percona Server users have been feeding into pt-query-digest for years. The idea is simple: reuse the tried-and-true tools and concepts we have been using for performing full … Continued<br />
The post Bringing pt-query-digest-Style Slow Query Analysis to PostgreSQL with pg_enhanced_query_logging appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/bringing-pt-query-digest-style-slow-query-analysis-to-postgresql-with-pg_enhanced_query_logging/">Bringing pt-query-digest-Style Slow Query Analysis to PostgreSQL with pg_enhanced_query_logging</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<div>In this blog post, we are going to briefly discuss <code><a href="https://github.com/guriandoro/pg_enhanced_query_logging">pg_enhanced_query_logging</a></code> (PEQL for short), a PostgreSQL extension that produces slow query logs in the same format MySQL and Percona Server users have been feeding into <code><a href="https://docs.percona.com/percona-toolkit/pt-query-digest.html">pt-query-digest</a></code> for years. The idea is simple: reuse the tried-and-true tools and concepts we have been using for performing full query audits with low performance hits. This tool was conceived and developed for the recent <a href="https://www.percona.com/blog/innovation-from-every-corner-inside-perconas-build-with-ai-competition/">Percona Build with AI Competition</a>.</div>
<div></div>
<div>A quick word of caution before we begin: PEQL is under active development and has not been validated for production use. We will use it in a development environment here, and you should do the same.</div>
<div></div>
<h2>Why a new slow log for PostgreSQL?<a class="anchor-link" id="why-a-new-slow-log-for-postgresql"></a></h2>
<div>Out of the box, PostgreSQL gives us <code>log_min_duration_statement</code> and a handful of related GUCs that print slow queries to the server log. That is useful, but the format is line-oriented and mixed in with everything else PostgreSQL writes there. On the MySQL side, the Percona Server <a href="https://docs.percona.com/percona-server/innovation-release/slow-extended.html">extended slow query log</a> goes much further: per-query counters, lock and I/O times, plan-quality flags, and a structured format that <code>pt-query-digest</code> can group by query fingerprint and rank by total time, average time, lock time, etc. This introduces the more powerful concept of performance of a family of queries, and not just individual query executions.</div>
<div>PEQL ports that same workflow to PostgreSQL. It hooks into the executor and planner, captures timing, buffer I/O, WAL, JIT and row-count metrics for every query slower than a configurable threshold, and writes them to a dedicated log file using a pt-query-digest-compatible format.</div>
<div></div>
<h2>Motivation and benefits<a class="anchor-link" id="motivation-and-benefits"></a></h2>
<div>The original idea behind this extension is doing query audits with minimal impact on the running server. We want to be able to ask &ldquo;what queries will we benefit more from tuning?&rdquo; without paying for it in latency, I/O or in a flood of unrelated log lines.</div>
<div>That goal drives most of the design decisions:</div>
<ul>
<li><strong>Statistically accurate sampling with low overhead.</strong> We don&rsquo;t need to log every single query to draw useful conclusions. PEQL can sample 1 out of every N queries (or 1 out of every N sessions), and doing this for enough time will mean that we can have a sample that represents the overall workload for that time period. The cost on the producer side stays low even on busy servers.</li>
<li><strong><em>pt-query-digest</em> compatibility out of the box.</strong> The output format mirrors the MySQL/Percona Server slow log, so the same toolchain we already use for MySQL audits works for PostgreSQL with no extra steps.</li>
<li><strong>Logging to a separate file.</strong> All entries go to a dedicated file (default <code>peql-slow.log</code>), not to PostgreSQL&rsquo;s main error log. That keeps the error log clean for actual errors and lets us point the slow log at a separate mountpoint if we want to isolate its I/O from the rest of the server.</li>
<li><strong>Rate limiting by both queries and bytes per second.</strong> On top of the per-session/per-query 1-in-N sampling, <code>peql.rate_limit_auto_max_queries</code> and <code>peql.rate_limit_auto_max_bytes</code> give us a cluster-wide cap on logged queries per second and on bytes written per second. Useful for guaranteeing that the slow log itself never becomes a performance issue.</li>
<li><strong>Always-log override for slow outliers.</strong> Even when sampling is on, <code>peql.rate_limit_always_log_duration</code> lets us say &ldquo;but always log anything that takes longer than X ms&rdquo;. The common queries get randomly sampled; the long-running ones always get logged.</li>
<li><strong>Extended resource usage metrics.</strong> Each entry includes buffer hit/read/dirtied/written counts (shared, local and temp), block I/O timings, WAL records/bytes/full-page images, JIT compilation timings, planning time, optional memory context allocations and an optional wait-event histogram.</li>
<li><strong>Execution plans embedded in the entry.</strong> With <code>peql.log_query_plan = on</code>, the full <code>EXPLAIN ANALYZE</code> output (text or JSON) is appended to each entry, so the plan that produced the metrics is right there next to them when we are reviewing the log later.</li>
<li><strong>Automatic pause when disk space is low.</strong> If the log mountpoint drops below a configurable free-space threshold, PEQL pauses logging on its own (with optional auto-purge of old rotated files) and resumes once there is room again. The database keeps serving traffic; the slow log gets out of the way.</li>
</ul>
<p>&nbsp;</p>
<h2>Installing the extension<a class="anchor-link" id="installing-the-extension"></a></h2>
<div>PEQL is a regular PGXS extension, to build it we can execute the following steps:</div>
<div>
<pre class="urvanov-syntax-highlighter-plain-tag">git clone https://github.com/guriandoro/pg_enhanced_query_logging.git
cd pg_enhanced_query_logging
make USE_PGXS=1
sudo make install USE_PGXS=1</pre>
</div>
<div>This installs the shared library into <code>$(pg_config --pkglibdir)</code> and the SQL/control files into <code>$(pg_config --sharedir)/extension/</code>. The hooks live in the shared library, so we need to preload it. Add the following line to <code>postgresql.conf</code> (or edit your current value to include it):</div>
<div>
<pre class="urvanov-syntax-highlighter-plain-tag">shared_preload_libraries = 'pg_enhanced_query_logging'</pre>
</div>
<div>Restart PostgreSQL, and then create the extension in any database where we want the SQL helper functions:</div>
<div>
<pre class="urvanov-syntax-highlighter-plain-tag">CREATE EXTENSION pg_enhanced_query_logging;</pre>
</div>
<div>To easily test it, the repository ships a Docker-based quick start that builds Rocky Linux 9 + PostgreSQL 18 with the extension preloaded:</div>
<div>
<pre class="urvanov-syntax-highlighter-plain-tag">./test/deploy_docker_pg18_rhel.sh</pre>
</div>
<p>&nbsp;</p>
<h2>A minimal configuration<a class="anchor-link" id="a-minimal-configuration"></a></h2>
<div>For a first look, the easiest thing to do is to log every query at full verbosity:</div>
<div>
<pre class="urvanov-syntax-highlighter-plain-tag">shared_preload_libraries = 'pg_enhanced_query_logging'
peql.log_min_duration = 0 # log every query
peql.log_verbosity = 'full' # emit all metric lines</pre>
</div>
<div>While we are at it, we can also silence PostgreSQL&rsquo;s native query logging so we have a single place to look:</div>
<div>
<pre class="urvanov-syntax-highlighter-plain-tag">log_statement = 'none'
log_min_duration_statement = -1
log_duration = off</pre>
</div>
<div>By default, PEQL writes to <code>peql-slow.log</code> inside PostgreSQL&rsquo;s <code>log_directory</code>. The location and filename are configurable via <code>peql.log_directory</code> and <code>peql.log_filename</code>.</div>
<div></div>
<h2>What an entry looks like<a class="anchor-link" id="what-an-entry-looks-like"></a></h2>
<div>After running a few queries, opening <code>peql-slow.log</code> shows entries like this one (trimmed for brevity):</div>
<div>
<pre class="urvanov-syntax-highlighter-plain-tag"># Time: 2026-03-11T09:15:32.847291
# User@Host: app_user[app_user] @ 10.0.1.42 []
# Thread_id: 48712 Schema: mydb.public
# Query_id: -6432758210044805760
# Query_time: 1.285034 Lock_time: 0.000000 Rows_sent: 256 Rows_examined: 87500
# Shared_blks_hit: 4096 Shared_blks_read: 312 Shared_blks_dirtied: 0 Shared_blks_written: 0
# Temp_blks_read: 0 Temp_blks_written: 48
# Shared_blk_read_time: 0.024310 Shared_blk_write_time: 0.000000
# WAL_records: 0 WAL_bytes: 0 WAL_fpi: 0
# Plan_time: 0.003210
# Full_scan: Yes Temp_table: No Temp_table_on_disk: Yes Filesort: Yes Filesort_on_disk: No
# JIT_functions: 4 JIT_generation_time: 0.001250 JIT_emission_time: 0.003100
SET timestamp=1741680931;
SELECT o.id, o.total, c.name FROM orders o JOIN customers c ON c.id = o.customer_id
WHERE o.status = 'pending' ORDER BY o.total DESC LIMIT 256;</pre>
<p>
The full breakdown of every field, with the GUCs that produce it, lives in <a href="https://github.com/guriandoro/pg_enhanced_query_logging/blob/master/doc/annotated-sample.md">doc/annotated-sample.md</a>. This is a great place to start reading the documentation.</p>
<p>&nbsp;</p>
</div>
<h2>Feeding it to pt-query-digest<a class="anchor-link" id="feeding-it-to-pt-query-digest"></a></h2>
<div>Because the format mirrors the MySQL slow log, we can point <code>pt-query-digest</code> at it directly:</div>
<div>
<pre class="urvanov-syntax-highlighter-plain-tag">pt-query-digest --type slowlog $(pg_config --logdir)/peql-slow.log</pre>
</div>
<div>We get the familiar profile at the top (queries grouped by fingerprint, ranked by total time), followed by the per-query detail blocks. The plan-quality flags above can also be used as filters, for example to look only at queries that did a sequential scan:</div>
<div>
<pre class="urvanov-syntax-highlighter-plain-tag">pt-query-digest --type slowlog 
--filter '$event-&gt;{Full_scan} eq "Yes"' 
$(pg_config --logdir)/peql-slow.log</pre>
<p>
If you do not have <code>pt-query-digest</code> installed, the standalone script can be downloaded directly:</p>
</div>
<div>
<pre class="urvanov-syntax-highlighter-plain-tag">curl -LO https://percona.com/get/pt-query-digest
chmod +x pt-query-digest</pre>
</div>
<p><span style="font-weight: 400">Example <code>pt-query-digest</code></span><span style="font-weight: 400">&nbsp;outputs will look like the following images.</span></p>
<figure id="attachment_45325" aria-describedby="caption-attachment-45325" style="width: 1024px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="wp-image-45325 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/global-1024x868.png" alt="" width="1024" height="868"><figcaption id="caption-attachment-45325" class="wp-caption-text">Queries grouped by fingerprint, ranked by total time.</figcaption></figure>
<figure id="attachment_45326" aria-describedby="caption-attachment-45326" style="width: 980px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="wp-image-45326 size-large" src="https://www.percona.com/wp-content/uploads/2026/05/query-980x1024.png" alt="" width="980" height="1024"><figcaption id="caption-attachment-45326" class="wp-caption-text">Per-query detail blocks.</figcaption></figure>
<p>&nbsp;</p>
<h2>A few useful knobs<a class="anchor-link" id="a-few-useful-knobs"></a></h2>
<div>Once we move beyond logging everything, there are a handful of GUCs worth knowing about:</div>
<ul>
<li><code>peql.rate_limit</code>: 1-in-N sampling, either per session or per query, with a <code>peql.rate_limit_always_log_duration</code> override so that very slow queries are always captured even when sampling is on.</li>
<li><code>peql.log_parameter_values</code>: include actual bind parameter values for prepared statements alongside the placeholder query text.</li>
<li><code>peql.log_query_plan</code>: embed the full <code>EXPLAIN ANALYZE</code> output (text or JSON) inside the log entry, so the plan that produced the metrics is right there next to them. This can be expensive in terms of I/O, so use sparingly and only if needed.</li>
</ul>
<div>The full list, with default values and contexts, is documented in <a href="https://github.com/guriandoro/pg_enhanced_query_logging/blob/master/doc/configuration.md">doc/configuration.md</a>.</div>
<div></div>
<h2>Future work<a class="anchor-link" id="future-work"></a></h2>
<div>The <code>pt-query-digest</code> compatibility is a feature, but it&rsquo;s also a constraint: the MySQL slow log format was <span style="font-weight: 400">designed to be human readable</span>, which means it&rsquo;s way too verbose. For instance, the plan-quality flags line only has 5 bits of actual information, but uses around 100 bytes to encode them:</div>
<div>
<pre class="urvanov-syntax-highlighter-plain-tag"># Full_scan: Yes Temp_table: No Temp_table_on_disk: No Filesort: Yes Filesort_on_disk: No</pre>
</div>
<div>We can do this better by simply logging <code>YNNYN</code> or <code>10010</code> (hence the 5 bits of information mentioned above), and have the position within the query log entry make it self-explanatory as to what this information is.</div>
<div></div>
<div>This is a 20x amplification factor! And other lines suffer of similar issues&hellip; Multiply that by every query on a busy server and the overhead adds up quickly, both in disk space and in the I/O the backend has to do to write the entries out.</div>
<div></div>
<div>There are two pieces of follow-up work we have in mind to address this:</div>
<ol>
<li><strong>A PEQL-native log format.</strong> A more compact, structured format (think key-value pairs with short keys, bitfields for the boolean flags, or a binary framing for the numeric metrics) that drops the bytes-per-query cost without losing any of the information we currently emit. The verbose pt-query-digest-compatible format could still be available for users that want it; the native format would be the recommended option for high-throughput workloads.</li>
<li><strong>Tooling for the new format.</strong> Once the native format exists, we will either contribute a parser to <code>pt-query-digest</code> so that it can ingest it natively (<code>--type peql</code> or similar), or ship a small companion tool that either post-processes them or produces the same kind of profile reports <code>pt-query-digest</code> does today. Either way, the goal is to keep the analysis workflow we are used to while removing the format-imposed overhead from the producer side.</li>
</ol>
<div>If any of this sounds interesting and you would like to help shape it, the repository&rsquo;s <a href="https://github.com/guriandoro/pg_enhanced_query_logging/blob/master/doc/contributing.md">doc/contributing.md</a> is the right place to start.</div>
<div></div>
<h2>Conclusion<a class="anchor-link" id="conclusion"></a></h2>
<div>PostgreSQL has had rich per-query metrics available for a while now, but stitching them together into the kind of &ldquo;show me the worst-performing family of queries from the last hour&rdquo; workflow MySQL users have enjoyed for years has taken more effort. PEQL closes that gap by emitting a single, pt-query-digest-compatible log file with timing, buffer, WAL, JIT and plan-quality data attached to every query.</div>
<div></div>
<div>If you want to dig deeper, the <a href="https://github.com/guriandoro/pg_enhanced_query_logging/tree/master/doc">doc/</a> directory in the repository has detailed pages on the output format, the architecture of the hooks, the rate limiter and the disk-space protection logic. And if you have not used <code>pt-query-digest</code>&nbsp;before, this is a great time to do it!</div>
<p>The post <a href="https://www.percona.com/blog/bringing-pt-query-digest-style-slow-query-analysis-to-postgresql-with-pg_enhanced_query_logging/">Bringing pt-query-digest-Style Slow Query Analysis to PostgreSQL with pg_enhanced_query_logging</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/bringing-pt-query-digest-style-slow-query-analysis-to-postgresql-with-pg_enhanced_query_logging/">Bringing pt-query-digest-Style Slow Query Analysis to PostgreSQL with pg_enhanced_query_logging</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Meet the Percona Community team</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/05/07/meet-the-percona-community-team/" />
      <id>https://percona.community/blog/2026/05/07/meet-the-percona-community-team/</id>
      <updated>2026-05-07T09:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>We’ve just landed on X and Mastodon, and before the first real post goes out, we wanted to do something we don’t do often enough: introduce ourselves.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/07/meet-the-percona-community-team/">Meet the Percona Community team</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>We&rsquo;ve just landed on X and Mastodon, and before the first real post goes out, we wanted to do something we don&rsquo;t do often enough: introduce ourselves.</p>
<p>If you&rsquo;ve been to Percona Live, a Percona.connect, a PGConf, KubeCon, FOSDEM, or pretty much any open source database event in the past few years, you&rsquo;ve probably already met one of us. We&rsquo;re the people behind the booth, on stage, organising the speakers, herding the giant Jenga set, or trying to convince you to play a quick game of chess between sessions. Now we&rsquo;re also the people behind @PerconaCommunity on X and our new Mastodon account on the fediverse.</p>
<p>Each of us will sign our posts with our initials, so you&rsquo;ll always know who you&rsquo;re talking to. Here&rsquo;s who we are.</p>
<h2 id="laura-czajkowski---director-of-community-lc">Laura Czajkowski &ndash; Director of Community (LC)<a class="anchor-link" id="laura-czajkowski-director-of-community-lc"></a></h2>
<p>Laura runs the team. She&rsquo;s been in open source community work since the early 2000s, starting at the University of Limerick&rsquo;s Skynet computer society and going on to lead community at Canonical (Ubuntu), MongoDB, Couchbase, Vonage, Solace, and Dragonfly before joining Percona. Former Ubuntu LoCo Council and Community Council member. Outside work she&rsquo;s a Munster and Ireland rugby fan, runs a book club, plays tennis, and books regular trips to Disney World. Find her at <a href="https://laura.community" target="_blank" rel="noopener noreferrer">laura.community</a>.</p>
<h2 id="alastair-turner---postgres-community-advocate-at">Alastair Turner &ndash; Postgres Community Advocate (AT)<a class="anchor-link" id="alastair-turner-postgres-community-advocate-at"></a></h2>
<p>Alastair has been working with databases since 1995, settling on Postgres around 2002. If you&rsquo;ve spoken to anyone at Percona about PostgreSQL, Kubernetes, Transparent Data Encryption, or extensions, there&rsquo;s a good chance it was him. He writes regularly on the <a href="https://percona.community/" target="_blank" rel="noopener noreferrer">Percona Community blog</a> and speaks at PGConf events across Europe and North America. He&rsquo;s particularly interested in how open source communities work together &ndash; and what they can learn from each other.</p>
<h2 id="daniil-bazhenov---senior-community-manager-db">Daniil Bazhenov &ndash; Senior Community Manager (DB)<a class="anchor-link" id="daniil-bazhenov-senior-community-manager-db"></a></h2>
<p>Daniil organises our conference speakers, runs the Percona Forums, and has been a long-time contributor to the Percona Community blog. If you&rsquo;ve ever submitted a talk to Percona Live or asked a question on forums.percona.com, you&rsquo;ve crossed paths with him. He writes hands-on technical content too &ndash; GitOps with ArgoCD, PMM monitoring, Percona Everest from source &ndash; and hosts the Russian-language Percona Podcast.</p>
<h2 id="kyle-flanagan---global-manager-events-kf">Kyle Flanagan &ndash; Global Manager, Events (KF)<a class="anchor-link" id="kyle-flanagan-global-manager-events-kf"></a></h2>
<p>Kyle is the reason any of our events actually happen. He runs Percona&rsquo;s global events programme, from Percona Live and Percona.connect to our presence at Open Source Summit, KubeCon, and dozens of regional events each year. Before Percona, he ran executive events at Utah Valley University. If you&rsquo;ve grabbed a sticker at one of our booths, Kyle probably packed the box it came in.</p>
<h2 id="edith-puclla---technology-evangelist-ep">Edith Puclla &ndash; Technology Evangelist (EP)<a class="anchor-link" id="edith-puclla-technology-evangelist-ep"></a></h2>
<p>Originally from Peru, now based in London, Edith is a CNCF Ambassador, Docker Captain, and Data on Kubernetes Ambassador. Her background is in DevOps and infrastructure &ndash; Kubernetes, GPUs, Linux, distributed systems &ndash; and she contributes to translating Kubernetes documentation into Spanish through SIG-Operators. She&rsquo;s a regular speaker at FOSDEM, KubeCon, Cloud Native Rejekts, and Percona University events across Latin America.</p>
<h2 id="why-were-doing-this">Why we&rsquo;re doing this<a class="anchor-link" id="why-were-doing-this"></a></h2>
<p>We spend a lot of our time at events because that&rsquo;s where the most useful conversations happen &ndash; the ones over coffee, at the booth, in the hallway between talks. Being on social gives us a way to keep those conversations going when we&rsquo;re not in the same room. Expect event updates, contributor shout-outs, things we&rsquo;ve found useful, and the occasional opinion. If we&rsquo;ve shared it, we&rsquo;ve actually read it.</p>
<p>Photo below was taken at our recent team offsite in Antalya &ndash; five people who genuinely like working together, in case the smiles don&rsquo;t give it away.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/05/community-team-with-names.jpeg" alt="The Percona Community team in Antalya"></figure>
</p>
<p><strong>Find us:</strong></p>
<ul>
<li>X: <a href="https://x.com/PerconaBytes" target="_blank" rel="noopener noreferrer">@PerconaBytes</a></li>
<li>Mastodon: <a href="https://mastodon.social/@PerconaBytes" target="_blank" rel="noopener noreferrer">@PerconaBytes</a></li>
<li>Forums: <a href="https://forums.percona.com" target="_blank" rel="noopener noreferrer">forums.percona.com</a></li>
<li>Community blog: <a href="https://percona.community" target="_blank" rel="noopener noreferrer">percona.community</a></li>
</ul>
<p>Come say hi. If we&rsquo;re at an event near you, the booth is open &ndash; and so is the giant Jenga.</p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/07/meet-the-percona-community-team/">Meet the Percona Community team</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Adding a New Data Type to MariaDB with Type_handler – Part 5</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-5/" />
      <id>https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-5/</id>
      <updated>2026-05-06T13:51:44+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>We are concluding our series related to new data types using the Type_handler framework, with some limitations that are not yet covered by the framework:<br />
It would have been handy for our MONEY datatype to have the possibility to define, for example, the currency to show. …<br />
Continue reading \"Adding a New Data Type to MariaDB with Type_handler – Part 5\"<br />
The post Adding a New Data Type to MariaDB with Type_handler – Part 5 appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-5/">Adding a New Data Type to MariaDB with Type_handler – Part 5</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>We are concluding our series related to new data types using the Type_handler framework, with some limitations that are not yet covered by the framework:<br>
It would have been handy for our MONEY datatype to have the possibility to define, for example, the currency to show. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-5/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Adding a New Data Type to MariaDB with Type_handler &ndash; Part 5&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-5/">Adding a New Data Type to MariaDB with Type_handler &ndash; Part 5</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-5/">Adding a New Data Type to MariaDB with Type_handler – Part 5</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>PSMDB Sandbox: A Browser-Based UI for Deploying MongoDB with Terraform and Ansible</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/psmdb-sandbox-a-browser-based-ui-for-deploying-mongodb-with-terraform-and-ansible/" />
      <id>https://www.percona.com/blog/psmdb-sandbox-a-browser-based-ui-for-deploying-mongodb-with-terraform-and-ansible/</id>
      <updated>2026-05-05T17:12:09+00:00</updated>
      <author><name>Ivan Groenewold</name></author>
      <summary type="html"><![CDATA[<p>If you’ve ever wrestled with .tfvars files, juggled Ansible inventory paths, or tried to remember the exact command sequence for a MongoDB setup — this post is for you. PSMDB Sandbox is a lightweight web frontend built in Go that ships inside the Percona MongoDB Automation repository. It puts a clean browser interface on top … Continued<br />
The post PSMDB Sandbox: A Browser-Based UI for Deploying MongoDB with Terraform and Ansible appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/psmdb-sandbox-a-browser-based-ui-for-deploying-mongodb-with-terraform-and-ansible/">PSMDB Sandbox: A Browser-Based UI for Deploying MongoDB with Terraform and Ansible</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">If you&rsquo;ve ever wrestled with </span><span style="font-weight: 400">.tfvars</span><span style="font-weight: 400"> files, juggled Ansible inventory paths, or tried to remember the exact command sequence for a MongoDB setup &mdash; this post is for you.</span></p>
<p><a href="https://github.com/percona/mongo_terraform_ansible/tree/main/ui-go"><span style="font-weight: 400">PSMDB Sandbox</span></a><span style="font-weight: 400"> is a lightweight web frontend built in Go that ships inside the </span><a href="https://github.com/percona/mongo_terraform_ansible"><span style="font-weight: 400">Percona MongoDB Automation</span></a><span style="font-weight: 400"> repository. It puts a clean browser interface on top of the full Terraform + Ansible automation stack, so you can spin up, manage, and tear down MongoDB environments without ever touching a config file by hand.</span></p>
<p><span style="font-weight: 400">This project was built using vibe coding &mdash; the result is a fully functional application developed rapidly without writing every line from scratch. It&rsquo;s a great example of how AI-assisted development can accelerate tooling projects that would otherwise sit in the backlog forever.</span></p>
<h2><span style="font-weight: 400">Why a Web UI?</span><a class="anchor-link" id="why-a-web-ui"></a></h2>
<p><span style="font-weight: 400">The </span><a href="https://www.percona.com/blog/deploying-mongodb-test-environments-with-terraform-and-ansible/"><span style="font-weight: 400">mongo_terraform_ansible</span><span style="font-weight: 400"> project already automates a lot</span></a><span style="font-weight: 400">: it can deploy Percona Server for MongoDB (PSMDB), Percona Backup for MongoDB (PBM), and Percona Monitoring and Management (PMM</span><b>)</b><span style="font-weight: 400"> across AWS, GCP, Azure, Docker, and Libvirt/KVM. That&rsquo;s powerful &mdash; but the workflow traditionally meant editing </span><span style="font-weight: 400">.tfvars</span><span style="font-weight: 400"> files, running commands in the right order, and tracking state in your head.</span></p>
<p><span style="font-weight: 400">The Go UI changes that. It wraps the same Terraform and Ansible automation in a wizard-style interface, streams live output to your browser, and keeps track of environment state so you always know what&rsquo;s running, stopped, or in progress.</span></p>
<p><span style="font-weight: 400">It&rsquo;s particularly useful as a testing sandbox for PSMDB features. You can quickly spin up a replica set or sharded cluster, test backup and restore workflows with PBM, explore audit logging, and observe everything through PMM monitoring &mdash; all from the browser, and all torn down just as easily when you&rsquo;re done.</span></p>
<h2><span style="font-weight: 400">What You Can Configure</span><a class="anchor-link" id="what-you-can-configure"></a></h2>
<h3><span style="font-weight: 400">Cluster Topology</span><a class="anchor-link" id="cluster-topology"></a></h3>
<p><span style="font-weight: 400">Define how many clusters and replica sets you want, the number of nodes per replica set, and whether to deploy a sharded cluster or a simple replica set. Each cluster is independently configurable.</span></p>
<h3><span style="font-weight: 400">PSMDB Version and Packages</span><a class="anchor-link" id="psmdb-version-and-packages"></a></h3>
<p><span style="font-weight: 400">Pick the exact Percona Server for MongoDB release you want to test &mdash; package identifiers are fetched automatically from the Percona repository listing on startup, so you&rsquo;re always selecting from what&rsquo;s genuinely available. For Docker-based environments, image tags are pulled live from Docker Hub and cached for five minutes.</span></p>
<h3><span style="font-weight: 400">Backup and Restore with PBM</span><a class="anchor-link" id="backup-and-restore-with-pbm"></a></h3>
<p><span style="font-weight: 400">Percona Backup for MongoDB (PBM) can be included in the deployment. PBM is configured with the native storage backend for the supported environments (e.g. an S3 bucket is automatically created for AWS). This makes the sandbox ideal for testing backup policies, point-in-time recovery, and restore scenarios without touching production.</span></p>
<h3><span style="font-weight: 400">PMM Monitoring</span><a class="anchor-link" id="pmm-monitoring"></a></h3>
<p><span style="font-weight: 400">You can include a PMM Server in your environment so every PSMDB node is monitored from the moment it comes up. This makes it straightforward to test alerting rules, explore query analytics, or simply validate that your monitoring setup looks right before applying it elsewhere.</span></p>
<h2><span style="font-weight: 400">Live Deployment Logs</span><a class="anchor-link" id="live-deployment-logs"></a></h2>
<p><span style="font-weight: 400">When you hit </span><b>Deploy</b><span style="font-weight: 400">, the UI kicks off </span><span style="font-weight: 400">terraform init &amp;&amp; terraform apply</span><span style="font-weight: 400"> (plus Ansible playbooks for cloud platforms) in a background goroutine and streams the output directly to your browser via Server-Sent Events. No more tailing log files in a separate terminal.</span></p>
<h2><span style="font-weight: 400">Hosts &amp; Connections Panel</span><a class="anchor-link" id="hosts-connections-panel"></a></h2>
<p><span style="font-weight: 400">After a successful deployment, the environment detail page shows every host (or container) with:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Its IP address</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">A ready-to-copy connect command (</span><span style="font-weight: 400">ssh user@host</span><span style="font-weight: 400"> or </span><span style="font-weight: 400">docker exec -it &lt;name&gt; bash</span><span style="font-weight: 400">)</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">MongoDB connection strings for every replica set and cluster</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Clickable </span><b>Open</b><span style="font-weight: 400"> buttons for PMM and MinIO Console URLs</span></li>
</ul>
<h2><span style="font-weight: 400">Stop, Restart, Reset, and Destroy</span><a class="anchor-link" id="stop-restart-reset-and-destroy"></a></h2>
<p><span style="font-weight: 400">Full lifecycle management is available from the UI. For Docker environments, Stop and Restart call </span><span style="font-weight: 400">docker stop</span><span style="font-weight: 400"> / </span><span style="font-weight: 400">docker restart</span><span style="font-weight: 400"> filtered by the environment&rsquo;s prefix. For cloud environments, the corresponding Ansible </span><span style="font-weight: 400">stop.yml</span><span style="font-weight: 400"> and </span><span style="font-weight: 400">restart.yml </span><span style="font-weight: 400">playbooks run. Destroy calls </span><span style="font-weight: 400">terraform destroy</span><span style="font-weight: 400"> and, on success, automatically cleans up the inventory and redirects you back to the environments list.</span></p>
<h2><b>Getting Started</b><a class="anchor-link" id="getting-started"></a></h2>

<pre class="urvanov-syntax-highlighter-plain-tag">git clone https://github.com/percona/mongo_terraform_ansible.git
cd mongo_terraform_ansible/ui-go
go run .</pre>
<p><span style="font-weight: 400">Then open </span><a href="http://127.0.0.1:5001/"><span style="font-weight: 400">http://127.0.0.1:5001</span></a><span style="font-weight: 400"> in your browser.</span></p>
<p><span style="font-weight: 400">If you prefer a compiled binary:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">go build -o mongodeploy .
./mongodeploy</pre>
<p><span style="font-weight: 400">You can customize the bind address and port with environment variables:</span></p>
<p><b>Security note:</b><span style="font-weight: 400"> The UI is designed for local use. It binds to </span><span style="font-weight: 400">127.0.0.1</span><span style="font-weight: 400"> by default. Don&rsquo;t expose it to the public internet without adding authentication.</span></p>
<h2><b>Try It and Share Your Feedback</b><a class="anchor-link" id="try-it-and-share-your-feedback"></a></h2>
<p><span style="font-weight: 400">PSMDB Sandbox is a community-contributed tool. If you try it out, run into issues, or have ideas for improvements, open an issue or pull request on GitHub. The project is licensed under Apache 2.0.</span></p>
<p><span style="font-weight: 400">Happy deploying!</span></p>
<p>&nbsp;</p>
<p>The post <a href="https://www.percona.com/blog/psmdb-sandbox-a-browser-based-ui-for-deploying-mongodb-with-terraform-and-ansible/">PSMDB Sandbox: A Browser-Based UI for Deploying MongoDB with Terraform and Ansible</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/psmdb-sandbox-a-browser-based-ui-for-deploying-mongodb-with-terraform-and-ansible/">PSMDB Sandbox: A Browser-Based UI for Deploying MongoDB with Terraform and Ansible</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Adding a New Data Type to MariaDB with Type_handler – Part 4</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-4/" />
      <id>https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-4/</id>
      <updated>2026-05-05T09:30:53+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>This is part 4 of a series related to extending MariaDB with a custom data type using the Type_handler framework.<br />
You can find the previous articles below:<br />
Overriding Existing Types<br />
In the previous examples, our MONEY data type inherits from DOUBLE and then we override some methods. …<br />
Continue reading \"Adding a New Data Type to MariaDB with Type_handler – Part 4\"<br />
The post Adding a New Data Type to MariaDB with Type_handler – Part 4 appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-4/">Adding a New Data Type to MariaDB with Type_handler – Part 4</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>This is part 4 of a series related to extending MariaDB with a custom data type using the Type_handler framework.<br>
You can find the previous articles below:<br>
Overriding Existing Types<a id="overriding-existing-types"></a><br>
In the previous examples, our MONEY data type inherits from DOUBLE and then we override some methods. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-4/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Adding a New Data Type to MariaDB with Type_handler &ndash; Part 4&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-4/">Adding a New Data Type to MariaDB with Type_handler &ndash; Part 4</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-4/">Adding a New Data Type to MariaDB with Type_handler – Part 4</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>How I Stopped Babysitting My Coding Agent (With Dotfiles)</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/05/05/how-i-stopped-babysitting-my-coding-agent-with-dotfiles/" />
      <id>https://percona.community/blog/2026/05/05/how-i-stopped-babysitting-my-coding-agent-with-dotfiles/</id>
      <updated>2026-05-05T00:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>Most developers at least try to use coding agents for development-related tasks, but babysitting LLMs and managing their permissions is no fun. Completely skipping permission checks is a dangerous idea on your main machine, and setting up containers or VMs for sandboxing is a pain. Can we do better?</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/05/how-i-stopped-babysitting-my-coding-agent-with-dotfiles/">How I Stopped Babysitting My Coding Agent (With Dotfiles)</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Most developers at least try to use coding agents for development-related tasks, but babysitting LLMs and managing their permissions is no fun.<br>
Completely skipping permission checks is a dangerous idea on your main machine, and setting up containers or VMs for sandboxing is a pain.<br>
Can we do better?</p>
<h3 id="the-autonomy-problem">The autonomy problem<a class="anchor-link" id="the-autonomy-problem"></a></h3>
<p>If you work in software development, you have most certainly heard the phrase:</p>
<blockquote>
<p>Let&rsquo;s just use an LLM to solve it!</p>
</blockquote>
<p>People tend to forget that it&rsquo;s a bit more complicated than this:<br>
anybody can easily use LLMs, of course, but using them properly is a different question.<br>
Ideally, we could all just download a simple tool, give it some instructions, and relax:</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/ai-gardening.png" alt="&nbsp;"></figure>
</p>
<div class="admonition admonition--warning">
<p>Disclaimer: your employer might not approve if you do gardening during work hours; I suggest choosing a different activity in this case!</p>
</div>
<p>In all seriousness, every panel in the above image contains details that people tend to ignore, which either results in inefficient workflows or the creation of slop.</p>
<p>We can&rsquo;t talk about all of them in one go; it would be overly long and complex.<br>
I&rsquo;ll only focus on panel 2:<br>
what can we do to ensure our uninterrupted <del>gardening</del> normal work?</p>
<p>If you simply download Claude/Codex and start using the CLI tool, VS Code extension, or anything else, you&rsquo;ll quickly get bored of all the babysitting.</p>
<blockquote>
<p>Hey, user, can I execute another slightly different <code>ls</code> command?</p>
</blockquote>
<p>Either you decide it isn&rsquo;t worth the effort because of all the interruptions, or you start blindly hitting Enter: &ldquo;of course I approve, it should be safe&hellip;&rdquo;</p>
<ol>
<li>Are you really thoroughly reviewing every command it throws at you?</li>
<li>Even that 100-line bash script the TUI doesn&rsquo;t display properly, because it wouldn&rsquo;t fit on the screen?</li>
<li>Have you ever seen an agent circumvent directory permissions by accessing the restricted files through a one-off script instead?</li>
</ol>
<p>Fine-grained permissions of course exist, and in theory, you could try to configure something like that.<br>
But let&rsquo;s be honest, most of us won&rsquo;t take the time, and we likely won&rsquo;t notice if (3) happens as part of a long script.</p>
<h3 id="let-the-ai-run-free">Let the AI run free<a class="anchor-link" id="let-the-ai-run-free"></a></h3>
<p>That&rsquo;s the point where you might discover the other option:<br>
completely disabling the permission system and letting the AI do whatever it wants.</p>
<p>Nothing can go wrong, it&rsquo;s only on your machine, right?</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/05/ai-running.png" alt="&nbsp;"></figure>
</p>
<p>Except that:</p>
<ul>
<li>it will also have full network access, both for reading and posting</li>
<li>it can read all your secrets: its own OAuth token, your SSH key, and so on&hellip;</li>
<li>do you load your SSH key into ssh-agent? That&rsquo;s convenient so you don&rsquo;t have to enter your password every time, but do you also have a hardware key you have to touch on every use, or can the AI force-push your repository and later say</li>
</ul>
<blockquote>
<p>You are absolutely right! I shouldn&rsquo;t have done that. If you have backups you can restore them with the following steps: &hellip;</p>
</blockquote>
<p>Or it might end up in any number of similar situations.<br>
Coding agents aren&rsquo;t malicious by design, but they can be subject to prompt injection from the web, or simply reach dumb conclusions.<br>
There&rsquo;s a good reason why Claude, for example, calls this option <code>--dangerously-skip-permissions</code>.</p>
<h3 id="put-them-in-a-cage">Put them in a cage!<a class="anchor-link" id="put-them-in-a-cage"></a></h3>
<p>The next obvious choice is to let them run free, but only within a cell:<br>
run the agent inside a container or virtual machine, where it can only access what you let it.</p>
<p>This, however, costs us some convenience, as we face new issues:</p>
<ul>
<li>If we completely separate the environment, we can&rsquo;t access it from our main system.<br>
Allowing AI tools to push to your repo without confirmation is a bad idea, but maybe you yourself should be able to push somehow?<br>
Or to verify the changes in a more complex, outside environment?</li>
<li>Our environment and the AI&rsquo;s environment are different&hellip; which means we have to set up both.<br>
I hope your project is easy to bootstrap, with proper scripting so you don&rsquo;t have to do this by hand.<br>
But is your development environment also easy to bootstrap?</li>
</ul>
<p>There are some existing, ready-to-use solutions: for example, both Claude Code and OpenAI Codex have support for <a href="https://containers.dev/" target="_blank" rel="noopener noreferrer">devcontainers</a>.<br>
If you want an easy setup, these can be an option.</p>
<p>However, I wanted more:<br>
to replicate my main setup exactly &ndash; the same compilers, tools, shell and editor settings, and so on.<br>
The AI tools should have the same executables available.<br>
If I have to edit or do something directly in the container, I shouldn&rsquo;t be surprised by something working differently.</p>
<p>That&rsquo;s when I remembered: I already have a <a href="https://github.com/dutow/dotfiles" target="_blank" rel="noopener noreferrer">dotfiles</a> repo. Can I make it even better for this use case?</p>
<h3 id="automate-all-the-things">Automate all the things!<a class="anchor-link" id="automate-all-the-things"></a></h3>
<p>The idea of dotfiles is simple:<br>
a repository where you store your configuration, so when you reinstall your system, or when you have to start using another one, you can quickly replicate your preferred settings.<br>
Editors, shells, git &ndash; everything works the same, without spending hours figuring it all out again.</p>
<p>The problem is that it usually only focuses on configuring an already properly installed system.<br>
When you only buy a new PC every few years, or system administrators already set up every server you have to use before your first login, this isn&rsquo;t a big issue.</p>
<p>But when you want to be able to quickly set up and iterate with throwaway systems?<br>
Then you need better automation!</p>
<p>This is also a solved problem; tools like Ansible and Puppet exist.</p>
<p>The idea is simple:</p>
<ul>
<li>instead of manually setting up your system, use an automation tool to install and configure everything</li>
<li>you can leverage free CI services to make sure that your scripts work when run on a clean system</li>
<li>while docker/podman traditionally uses its own setup scripting, it is possible to build an image using the same automation tool instead</li>
<li>the result? Main PC, containers, virtual machines, and quick VPS instances all behaving exactly the same way!</li>
</ul>
<p>The downside is, of course, that you either have to reinstall your main PC once your new setup is good enough, or accept that it will be slightly different until you do so.<br>
I went with the reinstall; it&rsquo;s easy once you have things working.</p>
<p>And if you don&rsquo;t know any of these tools?<br>
That&rsquo;s the best part &ndash; we&rsquo;re using AI, and AI knows them well.</p>
<h3 id="a-side-note-on-architecture">A side note on architecture<a class="anchor-link" id="a-side-note-on-architecture"></a></h3>
<p>The focus of this blog post is panel 2, not the others.<br>
But I want to at least mention that the architecture and human review, including design review, are as important as with any other AI-driven software project.</p>
<p>If you completely vibe-code it and create an unmaintainable, sloppy dotfiles configuration, you are going to regret it later. This is your everyday work environment.</p>
<p>After the initial idea, when I started to think more about my requirements, I quickly realized that I want something generic.</p>
<p>First, I want to install a different set of packages depending on where I am installing them: containers, WSL instances, or real machines.<br>
My laptop needs slightly different settings compared to my desktop.</p>
<p>Second, I want to be able to do this on multiple distributions.<br>
Previously it was really annoying when I had to debug a distro-specific bug, unless it happened to involve one of my primary Linux distributions.<br>
I am also using a different OS on my work laptop and personal desktop PC because of company requirements.</p>
<p>With a proper Ansible setup, I can make all of these work seamlessly, even autodetecting the environment, and verifying all important configurations on CI for every commit.</p>
<p>Your requirements will most likely be different.<br>
Think about these beforehand and structure your repository accordingly!</p>
<h3 id="containers-or-virtual-machines">Containers or virtual machines?<a class="anchor-link" id="containers-or-virtual-machines"></a></h3>
<p>So far I mentioned both as alternatives, and both have their pros and cons.</p>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>Container</th>
<th>Virtual machine</th>
</tr>
</thead>
<tbody>
<tr>
<td>Resource overhead</td>
<td>Low</td>
<td>Higher</td>
</tr>
<tr>
<td>Spin-up time</td>
<td>Seconds</td>
<td>Minutes</td>
</tr>
<tr>
<td>Host integration (mounts, networks)</td>
<td>Easy, direct</td>
<td>Network only</td>
</tr>
<tr>
<td>Isolation from host</td>
<td>Partial</td>
<td>Strong</td>
</tr>
<tr>
<td>GUI / IDE support</td>
<td>Limited, terminal-friendly</td>
<td>Full desktop</td>
</tr>
<tr>
<td>Privileged tools (GDB, GPU)</td>
<td>Extra capabilities required</td>
<td>Native, inside the VM</td>
</tr>
<tr>
<td>Credential storage</td>
<td>Shares host&rsquo;s filesystem</td>
<td>Must duplicate (SSH key, hardware key)</td>
</tr>
</tbody>
</table>
<p>For now, I went with containers.<br>
With a few helper scripts I can mount specific directories from the host OS, and I can also specify which docker/podman network the new container should join.<br>
This lets me start up my docker-compose development clusters directly on my main OS, and lets the agent access the development/test database and other containers for its work using the shared network.</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-0">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">dcont run --mount `pwd` --network hackorum_default --context main-dev</span></span></code></pre>
</div>
</div>
</div>
<p>I even have a <code>context</code> parameter, which lets me keep multiple independent AI configurations: different system-level CLAUDE.md, plugin set, hooks, and so on.<br>
Underneath, this is just a few specific mounts and symlinks, but the advantage is huge:</p>
<ul>
<li>I can quickly experiment without fearing that I&rsquo;ll break my main workflows</li>
<li>I have completely separate setups for development and review work, without them conflicting with each other</li>
</ul>
<p>The upsides are easy integration, lower resource overhead, and quicker spin-up.<br>
I can mount directories directly from the host, and easily interact with docker containers running on the host.</p>
<p>The downside comes from that same integration:<br>
everything is still on the host, and the more access you give to the container, the less secure it becomes.<br>
Tools like GDB and GPU access require extra privileges, and you might have to relax SELinux features for the container.</p>
<p>The privilege problem, and the possibility of giving the container too much access, is a real risk.<br>
Docker, which runs as root on the host, and rootless podman, which maps the container root to the current host user, behave very differently if something is misconfigured &ndash; but neither protects the data accessible to the running user.</p>
<p>You can tighten the defaults with flags like <code>--cap-drop=ALL</code>, <code>--security-opt=no-new-privileges</code>, and read-only mounts where possible, but these only narrow the attack surface; they don&rsquo;t fix what you mount in.<br>
Which means what you mount matters more than which runtime you pick.</p>
<h4 id="mounts-and-credentials">Mounts and credentials</h4>
<p><code>.env</code> files, for example, can be challenging:<br>
these can contain API keys, passwords, and other secrets required by the application, which ideally shouldn&rsquo;t be accessible to the coding agent.<br>
I started using two levels of them &ndash; one in the project folder with only generic data, and another one level above containing sensitive login information for external services.<br>
This way, when I mount the project folder, the container can&rsquo;t access the sensitive <code>.env</code> file.</p>
<p>There are also some special files to watch out for:<br>
mounting <code>/var/run/docker.sock</code> into the container, for example, can break the sandbox completely, as it grants access equivalent to root on the host.</p>
<h4 id="when-to-pick-a-vm-instead">When to pick a VM instead</h4>
<p>A container also isn&rsquo;t a full-fledged desktop.<br>
Personally, I am used to working in terminals; I like tools like tmux or neovim.<br>
But if you prefer desktop applications and IDEs, a full virtual machine might be a better option.</p>
<p>Full virtual machines aren&rsquo;t more difficult to set up and give you a complete GUI, but they raise a different question:<br>
how do you set everything up without accidental credential leaks?</p>
<p>You either rely on network synchronization between your main OS and the virtual machine &ndash; pushing to remotes only from the main OS &ndash; or you give the virtual machine a hardware key and store your SSH key on it.</p>
<p>Agents can of course always access and leak their own API keys; we can&rsquo;t do anything about that with 100% certainty.<br>
But we can aim to reduce their ability to leak anything else, by minimizing what they physically have access to.</p>
<h3 id="an-example-setup">An example setup<a class="anchor-link" id="an-example-setup"></a></h3>
<p>You can check out my <a href="https://github.com/dutow/dotfiles" target="_blank" rel="noopener noreferrer">dotfiles</a> for inspiration.<br>
It should be only that:<br>
something you can look at while designing your own version.</p>
<p>It is designed for my workflows, and yours are most likely different.<br>
You also shouldn&rsquo;t blindly trust a script somebody else&rsquo;s LLM generated.</p>
<h4 id="the-helper-script">The helper script</h4>
<p>The repository has a readme; the most interesting part is probably <a href="https://github.com/dutow/dotfiles/blob/master/dcont" target="_blank" rel="noopener noreferrer">the script I mentioned earlier</a>, which builds and runs the containers.</p>
<p>It is long and complex, and deals with additional details I didn&rsquo;t even mention here, to keep this introduction from getting too involved.</p>
<p>The basic idea, however, is easy to summarize.<br>
A basic docker command is simple:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-1">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">docker run -it ubuntu:latest /bin/bash</span></span></code></pre>
</div>
</div>
</div>
<p>But it also gets complicated quickly:</p>
<ul>
<li>what folders need mounting? (the project, specific directories for tools)</li>
<li>which networks to join?</li>
<li>do we have to set up specific hardware, like a GPU?</li>
<li>do we need specific access permissions for some software?</li>
<li>and so on</li>
</ul>
<p>The command quickly becomes longer and longer, and copy-pasting it from notes or shell history isn&rsquo;t fun.<br>
It is also most likely project-specific.<br>
You want different mounts, different contexts for AIs, different specific permissions.<br>
All this should be configurable, and still simple.</p>
<p>In my case, most of my projects also have <code>.env</code> files set up, which makes it a no-brainer to also support configuration through environment variables.</p>
<p>Most of the time, all I have to do is <code>cd</code> into the project directory and execute <code>dcont</code> without any extra parameters. That starts up a ready-to-use, project-specific setup, and I can immediately start typing instructions to Claude.</p>
<h4 id="the-ansible-part">The Ansible part</h4>
<p>I already mentioned this before, but didn&rsquo;t go into the details:<br>
you can build docker or podman images with Ansible.</p>
<p>Normally this isn&rsquo;t that useful:<br>
if the only goal is a container cluster, a Containerfile is much easier to use, and more efficient for rebuilding, since it automatically detects which layers have to be rebuilt.</p>
<p>If the goal, however, is to replicate the same setup on a real host and in a container, the picture is different.<br>
These images are only meant for local use, so layering and image size don&rsquo;t matter &ndash; we&rsquo;ll never upload them.</p>
<p>Build times are also secondary.<br>
Even if a rebuild is needed once or twice a day, you can continue using the previous version in the meantime and switch later.<br>
And it&rsquo;s not like we can&rsquo;t do proper incremental builds with it; Ansible supports that too &ndash; it&rsquo;s just a bit slower than how containers normally do it.</p>
<p>This is included in the same script, and the solution is surprisingly simple:</p>
<ol>
<li>start a container with <code>sleep infinity</code></li>
<li>copy the dotfiles repo into it, since it&rsquo;s already checked out on the host</li>
<li>run the same dotfiles/Ansible script as on other hosts (with proper parameters)</li>
<li>set up a proper user</li>
<li>commit the image</li>
</ol>
<p>The same could be done using a <code>Containerfile</code>, but what&rsquo;s the advantage?<br>
The image isn&rsquo;t shareable or reusable anyway, and some operations are easier to implement directly in bash.<br>
This process also leaves open the possibility of doing incremental builds, instead of always rerunning the installation script from scratch.</p>
<h3 id="the-unsaid-part-network-access">The unsaid part: network access<a class="anchor-link" id="the-unsaid-part-network-access"></a></h3>
<p>In all of the sandboxing discussion above, I quietly ignored the question of network access:<br>
if you give unrestricted network access to an LLM agent, you can have a bad time.</p>
<p>Prompt injection exists, even if AI companies try to make it harder and harder.</p>
<p>For most use cases, a complete network ban is also a bad idea for productivity and code quality, which makes this another complex, open-ended question with its own options and tradeoffs &ndash; out of scope for this already long blog post.</p>
<p>I hope the information I provided here was useful, and that you can improve your AI setup based on it!</p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/05/how-i-stopped-babysitting-my-coding-agent-with-dotfiles/">How I Stopped Babysitting My Coding Agent (With Dotfiles)</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Long live to dbdeployer!</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/long-live-to-dbdeployer/" />
      <id>https://mariadb.org/long-live-to-dbdeployer/</id>
      <updated>2026-05-04T14:22:15+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>As you know, MySQL-Sandbox and then dbdeployer have always been part of the Swiss Army knife for DBAs trying to evaluate, test, or reproduce issues with a certain version of their database. …<br />
Continue reading \"Long live to dbdeployer!\"<br />
The post Long live to dbdeployer! appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/long-live-to-dbdeployer/">Long live to dbdeployer!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>As you know, MySQL-Sandbox and then dbdeployer have always been part of the Swiss Army knife for DBAs trying to evaluate, test, or reproduce issues with a certain version of their database. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/long-live-to-dbdeployer/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Long live to dbdeployer!&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/long-live-to-dbdeployer/">Long live to dbdeployer!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/long-live-to-dbdeployer/">Long live to dbdeployer!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>I Know Kung Fu</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/i-know-kung-fu/" />
      <id>https://www.percona.com/blog/i-know-kung-fu/</id>
      <updated>2026-05-04T12:43:46+00:00</updated>
      <author><name>Dennis Kittrell</name></author>
      <summary type="html"><![CDATA[<p>You might find this hard to believe, but AI has become kind of a thing around here. Bennie published a post on our Build with AI competition last week, in which he shared that I was lucky enough to land the second place prize. Genuinely flattered, and a real thank you to Peter F, PZ, … Continued<br />
The post I Know Kung Fu appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/i-know-kung-fu/">I Know Kung Fu</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>You might find this hard to believe, but AI has become kind of a thing around here.</p>
<p>Bennie published a post on our <a href="https://www.percona.com/blog/innovation-from-every-corner-inside-perconas-build-with-ai-competition/">Build with AI competition</a> last week, in which he shared that I was lucky enough to land the second place prize. Genuinely flattered, and a real thank you to Peter F, PZ, Vadim, and Bennie for organizing it. The recognition is great. But the part that does not quite come through in the recap is what those six weeks actually felt like from the inside. Forty-plus submissions, 10+ teams, marathon demo sessions that ran out of time twice over, and a constant drumbeat of ideas where every fifth one made me think &ldquo;wait, we can just&hellip; ship that?&rdquo;</p>
<p>Two submissions that really impressed me (and are worthy of high praise): Kedar Vaijanapurkar shipped <a href="https://nitty-witty.com/">a four-tool MySQL stack</a> (Advisor, random data generator, CleanPrompt, and a Query Reviewer), any one of which on its own would have been a strong submission. And Daniil built a leaderboard for Percona ecosystem contributors plus a vector-search prototype running on Percona&rsquo;s own products, which is exactly the dogfood story we want.</p>
<p>There were a lot more than three projects worth backing, which is part of why a second contest round is being coordinated later this year. A lot of the entries are not waiting for it either &ndash; they are already developing into real, operational utilities (some of mine included).</p>
<p>The two submissions of my own that I would point to first are <strong>IBEX</strong> and <strong>percona-dk</strong>.</p>
<p><strong>IBEX</strong> (Integration Bridge for EXtended systems) is a local MCP multi-tool server that connects either a local model or a Percona-owned LLM to the systems where the most valuable context actually lives. Slack, Notion, Jira, ServiceNow, Salesforce, etc. A solution was needed here since we could not point the standard Claude or ChatGPT connectors at our sensitive internal data, and obviously most of the context that makes LLMs so valuable is precisely that kind of data.</p>
<p><a href="https://github.com/Percona-Lab/percona-dk"><strong>percona-dk</strong></a> is the other one. It started as a way to keep AI honest about our own products by giving the AI tools our teams use (Claude, Cursor, anything that speaks MCP) direct access to Percona&rsquo;s documentation, so the answer to a question about our products comes from real docs with linked citations instead of stale training data or even scraped web results that can get things wrong. It has evolved a fair bit since the contest. The Percona Community blog and forums are now indexed alongside the docs, Perconians are getting real day-to-day value out of it, and it is starting to look like the kind of thing that could grow into a community utility (perhaps even beyond Percona docs).</p>
<p>Those two were just the start. Once IBEX worked, I needed shared memory across LLMs, so I built that. Once I had three MCP servers running, the boilerplate got annoying, so I built <a href="https://github.com/Percona-Lab/CAIRN"><strong>CAIRN</strong></a>, a scaffolding tool that builds on Anthropic&rsquo;s official MCP builder skill. The official skill walks you through writing a server step by step, but CAIRN spins up a complete, working project in minutes with a streamlined install wizard for non-technical users. It is now in the hands of other Perconians building their own MCP tools, and providing real value of its own. Then I learned about .mcpb files and Desktop Extensions (.dxt), packaged everything that way, and stood up an internal Claude plugin marketplace so any Perconian can install the lot from one place. Each layer opened a door I did not know existed until I was already through it. Some of those doors seemingly materialized from thin air as they magically aligned with new releases from Anthropic.</p>
<p>What started as a competition entry is now a small internal ecosystem. I am still a product person, not a software engineer. I am not going to pretend any of the code is pristine, and a lot of it was vibe-coded with Claude as a partner. But the architecture holds together, it works, and most of it is in daily use by people who are not me. That last part is the bit I am most proud of.</p>
<p>The next batch is pointed squarely at product operations. Making customer signals legible. Making internal telemetry something any teammate can talk to in plain English. The early returns are promising, and what gets me most excited is not the tech itself, it is watching people across Product, Engineering, and Support pull in the same direction with an AI colleague in the room. Turns out the interesting part of AI at work is not the model. It is the connective tissue.</p>
<h4>I know Kung Fu</h4>
<p>For a product guy who does not code for a living, this era is my &ldquo;I know kung fu&rdquo; moment. Not because I suddenly learned to fight. Because the move set I already had &ndash; product judgment, systems thinking, customer empathy, the ability to spec a thing precisely &ndash; just got a massive upgrade. The gap between &ldquo;that would be useful&rdquo; and &ldquo;that exists now&rdquo; is short enough to cross in an evening. I do not see it getting longer again.</p>
<p>Thanks for reading this far. If you want more detail or want to try anything not linked here, ping me. I am happy to share more.</p>
<p><!-- notionvc: fc8a336b-f88d-4d72-931c-1b57605ac9ad --></p>
<p>The post <a href="https://www.percona.com/blog/i-know-kung-fu/">I Know Kung Fu</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/i-know-kung-fu/">I Know Kung Fu</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Adding a New Data Type to MariaDB with Type_handler – Part 3</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-3/" />
      <id>https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-3/</id>
      <updated>2026-05-04T06:27:16+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>In the previous article, we wrote, compiled, and tested our first custom data type for MariaDB using the Type_handler framework.<br />
But currently, aside from allowing the use of its new name (MONEY) and listing it in the metadata, our new data type behaves exactly like a DOUBLE, the class it inherits from. …<br />
Continue reading \"Adding a New Data Type to MariaDB with Type_handler – Part 3\"<br />
The post Adding a New Data Type to MariaDB with Type_handler – Part 3 appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-3/">Adding a New Data Type to MariaDB with Type_handler – Part 3</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>In <a href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-2/">the previous article</a>, we wrote, compiled, and tested our first custom data type for MariaDB using the Type_handler framework.<br>
But currently, aside from allowing the use of its new name (MONEY) and listing it in the metadata, our new data type behaves exactly like a DOUBLE, the class it inherits from. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-3/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Adding a New Data Type to MariaDB with Type_handler &ndash; Part 3&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-3/">Adding a New Data Type to MariaDB with Type_handler &ndash; Part 3</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-3/">Adding a New Data Type to MariaDB with Type_handler – Part 3</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Curious case of PXC node that refused to start due to SSL</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/curious-case-of-pxc-node-that-refused-to-start-due-to-ssl/" />
      <id>https://www.percona.com/blog/curious-case-of-pxc-node-that-refused-to-start-due-to-ssl/</id>
      <updated>2026-05-04T05:45:15+00:00</updated>
      <author><name>Kedar Vaijanapurkar</name></author>
      <summary type="html"><![CDATA[<p>In this blog, I am going to share a real-world debugging case study where a routine Percona XtraDB Cluster node restart led to an unexpected failure. I will walk through what we observed, what we checked, and how we ultimately identified the root cause. Let’s see how the maintenance goes. It was supposed to be … Continued<br />
The post Curious case of PXC node that refused to start due to SSL appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/curious-case-of-pxc-node-that-refused-to-start-due-to-ssl/">Curious case of PXC node that refused to start due to SSL</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">In this blog, I am going to share a real-world debugging case study where a routine Percona XtraDB Cluster node restart led to an unexpected failure. I will walk through what we observed, what we checked, and how we ultimately identified the root cause.</span></p>
<p><span style="font-weight: 400">Let&rsquo;s see how the maintenance goes. It was supposed to be a simple restart. </span><span style="font-weight: 400">The kind you&rsquo;ve done a hundred times. You SSH in, run the maintenance, bring the node back up, and go grab a coffee. Except this time, the coffee went cold on the desk&hellip; because MySQL refused to start.</span></p>
<h2><span style="font-weight: 400">The Problem</span><a class="anchor-link" id="the-problem"></a></h2>
<p><span style="font-weight: 400">The error log of Percona XtraDB Cluster (8.0) had the following information:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">2025-11-05T05:26:10.982984Z 0 [ERROR] [MY-000059]   [Server] SSL error: Unable to get certificate from '/var/lib/mysql/server-cert.pem'.
2025-11-05T05:26:10.983030Z 0 [Warning] [MY-013595] [Server] Failed to initialize TLS for channel: mysql_main. See below for the description of exact issue.
2025-11-05T05:26:10.983045Z 0 [Warning] [MY-010069] [Server] Failed to set up SSL because of the following SSL library error: Unable to get certificate
2025-11-05T05:26:10.983052Z 0 [Note] [MY-000000] [WSREP] New joining cluster node configured to use specified SSL artifacts
2025-11-05T05:26:10.983083Z 0 [Note] [MY-000000] [Galera] Loading provider /usr/lib64/galera4/libgalera_smm.so initial position: 07c67757-0d18-11ef-b5a9-ee5d87b39aa8:4147053897
2025-11-05T05:26:10.983098Z 0 [Note] [MY-000000] [Galera] wsrep_load(): loading provider library '/usr/lib64/galera4/libgalera_smm.so'
2025-11-05T05:26:10.983742Z 0 [Note] [MY-000000] [Galera] wsrep_load(): Galera 4.22(f6c0465) by Codership Oy &lt;info@codership.com&gt; (modified by Percona &lt;https://percona.com/&gt;) loaded successfully.
2025-11-05T05:26:10.983771Z 0 [Note] [MY-000000] [Galera] Resolved symbol 'wsrep_node_isolation_mode_set_v1'
2025-11-05T05:26:10.983784Z 0 [Note] [MY-000000] [Galera] Resolved symbol 'wsrep_certify_v1'
2025-11-05T05:26:10.983807Z 0 [Note] [MY-000000] [Galera] CRC-32C: using 64-bit x86 acceleration.
2025-11-05T05:26:10.983995Z 0 [Note] [MY-000000] [Galera] not using SSL compression
2025-11-05T05:26:10.984341Z 0 [ERROR] [MY-000000] [Galera] Bad value '/var/lib/mysql/server-cert.pem' for SSL parameter 'socket.ssl_cert': 336245135: 'error:140AB18F:SSL routines:SSL_CTX_use_certificate:ee key too small'
         at /mnt/jenkins/workspace/pxc80-autobuild-RELEASE/test/rpmbuild/BUILD/Percona-XtraDB-Cluster-8.0.42/percona-xtradb-cluster-galera/galerautils/src/gu_asio.cpp:ssl_prepare_context():471
2025-11-05T05:26:10.984401Z 0 [ERROR] [MY-000000] [Galera] Failed to create a new provider '/usr/lib64/galera4/libgalera_smm.so' with options 'gcache.size=1G;gcache.recover=yes;socket.ssl=yes;socket.ssl_ca=/data00/mysqldata/ca.pem;socket.ssl_cert=/data00/mysqldata/server-cert.pem;socket.ssl_key=/data00/mysqldata/server-key.pem;socket.ssl_key=/var/lib/mysql/server-key.pem;socket.ssl_ca=/var/lib/mysql/ca.pem;socket.ssl_cert=/var/lib/mysql/server-cert.pem': Failed to initialize wsrep provider
2025-11-05T05:26:10.984434Z 0 [ERROR] [MY-000000] [WSREP] Failed to load provider
2025-11-05T05:26:10.984448Z 0 [ERROR] [MY-010119] [Server] Aborting
2025-11-05T05:26:10.984602Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.42-33.1)  Percona XtraDB Cluster (GPL), Release rel33, Revision 6673f8e, WSREP version 26.1.4.3.
2025-11-05T05:26:10.985473Z 0 [ERROR] [MY-010065] [Server] Failed to shutdown components infrastructure.</pre>
<p>&nbsp;</p>
<p><span style="font-weight: 400">MySQL was down, and the maintenance clock was running. The certificate file sitting at <code>/var/lib/mysql/server-cert.pem</code> was the same file that had been working perfectly fine before the restart!!<br>
</span><span style="font-weight: 400">From past history, it was known that the following commands were executed correctly on the same cluster node</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">SET GLOBAL ssl_ca = '/var/lib/mysql/ca.pem';
  SET GLOBAL ssl_cert = '/var/lib/mysql/server-cert.pem';
  SET GLOBAL ssl_key = '/var/lib/mysql/server-key.pem';
  ALTER INSTANCE RELOAD TLS;</pre>
<p><span style="font-weight: 400">Clients connected over TLS. Galera nodes communicated securely. There were zero complaints from the error log.<br>
</span><span style="font-weight: 400">In other words, the SSL reload at runtime inherited the process environment that existed when MySQL originally booted. Everything was smooth, but after a restart? MySQL complains and declines to start. So what has changed?</span></p>
<h2><span style="font-weight: 400">Checking Usual Suspects</span><a class="anchor-link" id="checking-usual-suspects"></a></h2>
<p><b>File permissions</b></p>
<p><span style="font-weight: 400">We checked the PEM files.&nbsp;</span></p>
<p><span style="font-weight: 400">Ownership: <code>mysql:mysql</code>.<br>
</span><span style="font-weight: 400">Permissions: <code>644</code> for the cert, <code>600</code> for the key.&nbsp;</span></p>
<p><span style="font-weight: 400">We compared them against the other Galera nodes, and they were identical. This didn&rsquo;t look like a permissions problem.</span></p>
<p><b>Is SELinux to blame here?</b></p>
<p><span style="font-weight: 400">SELinux has ruined enough DBA time that it is one of the top spots on such checklists &ndash; but it was permissive.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">$ getenforce
<span style="font-weight: 400">Permissive</span></pre>
<p><span style="font-weight: 400">That means it was logging any security issues, but not blocking. And there were no AVC denials related to MySQL or the PEM files in /var/log/audit/audit.log or dmesg!</span></p>
<p><b>File corruption</b></p>
<p><span style="font-weight: 400">Did the files get corrupted/replaced during or before the MySQL restart?</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">$ openssl x509 -in /var/lib/mysql/server-cert.pem -noout -text
# Output looked perfectly valid when compared to the output from other nodes

$ openssl rsa -in /var/lib/mysql/server-key.pem -check
RSA key ok</pre>
<p><span style="font-weight: 400">The files were fine. They parsed cleanly. OpenSSL could read them. So why couldn&rsquo;t MySQL?</span></p>
<p><b>More Logs review</b></p>
<p><span style="font-weight: 400">We scanned /var/log/messages and journalctl for anything unusual around the time of the restart. No disk errors. No OOM kills. No kernel panics. Nothing that screamed &ldquo;I am the Dhurandhar that&rsquo;s destroyed your node.&rdquo;&nbsp;</span><span style="font-weight: 400">At this point, most of the usual suspects were guilt-free, staring at us, asking, &ldquo;Who did it?&rdquo;</span></p>
<h2><span style="font-weight: 400">The Clue</span><a class="anchor-link" id="the-clue"></a></h2>
<p><span style="font-weight: 400">It is good to communicate with stakeholders, and we did &ndash; &ldquo;Was there any recent change on your side?&rdquo; to the client, and then uttered the golden words &ldquo;Last week the crypto-policy was updated on all of the DB servers to comply with PCI.&rdquo;</span></p>
<p><span style="font-weight: 400">PCI &gt; Crypto-policy &ndash; Let&rsquo;s go and check it !!</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">$ update-crypto-policies --show
FUTURE</pre>
<p><span style="font-weight: 400">The system was running RHEL&rsquo;s FUTURE cryptographic policy.</span></p>
<p><span style="font-weight: 400">For those unfamiliar (including me at the time), Red Hat Enterprise Linux (and its derivatives, such as Rocky, Alma, and Oracle Linux) ships with a system-wide cryptographic policy framework. It&rsquo;s a centralized way to enforce minimum standards for TLS versions, cipher suites, key lengths, and signature algorithms across all applications on the system that include OpenSS and yes, anything that links against those libraries&hellip; like MySQL.</span></p>
<p>Here&rsquo;s a table that shows information about the crypto-policy levels:</p>
<table class="alignleft" style="height: 237px;width: 622px;border-style: solid;border-color: #000000" border="1">
<tbody>
<tr style="height: 47px">
<td style="width: 62.1875px;height: 47px"><span style="font-weight: 400">Policy</span></td>
<td style="width: 92.3125px;height: 47px"><span style="font-weight: 400">RSA Minimum</span></td>
<td style="width: 91.3125px;height: 47px"><span style="font-weight: 400">TLS Minimum</span></td>
<td style="width: 101.344px;height: 47px"><span style="font-weight: 400">SHA-1 Signatures</span></td>
<td style="width: 240.844px;height: 47px"><span style="font-weight: 400">Use Case</span></td>
</tr>
<tr style="height: 42px">
<td style="width: 62.1875px;height: 42px"><span style="font-weight: 400">LEGACY</span></td>
<td style="width: 92.3125px;height: 42px"><span style="font-weight: 400">1024-bit</span></td>
<td style="width: 91.3125px;height: 42px"><span style="font-weight: 400">TLS 1.0</span></td>
<td style="width: 101.344px;height: 42px"><span style="font-weight: 400">Allowed</span></td>
<td style="width: 240.844px;height: 42px"><span style="font-weight: 400">Old systems compatibility</span></td>
</tr>
<tr style="height: 46px">
<td style="width: 62.1875px;height: 46px"><span style="font-weight: 400">DEFAULT</span></td>
<td style="width: 92.3125px;height: 46px"><span style="font-weight: 400">2048-bit</span></td>
<td style="width: 91.3125px;height: 46px"><span style="font-weight: 400">TLS 1.2</span></td>
<td style="width: 101.344px;height: 46px"><span style="font-weight: 400">Allowed</span></td>
<td style="width: 240.844px;height: 46px"><span style="font-weight: 400">Standard operations</span></td>
</tr>
<tr style="height: 44px">
<td style="width: 62.1875px;height: 44px"><span style="font-weight: 400">FUTURE</span></td>
<td style="width: 92.3125px;height: 44px"><span style="font-weight: 400">3072-bit</span></td>
<td style="width: 91.3125px;height: 44px"><span style="font-weight: 400">TLS 1.2</span></td>
<td style="width: 101.344px;height: 44px"><span style="font-weight: 400">Blocked</span></td>
<td style="width: 240.844px;height: 44px"><span style="font-weight: 400">Forward-looking hardening</span></td>
</tr>
<tr style="height: 58px">
<td style="width: 62.1875px;height: 58px"><span style="font-weight: 400">FIPS</span></td>
<td style="width: 92.3125px;height: 58px"><span style="font-weight: 400">2048-bit</span></td>
<td style="width: 91.3125px;height: 58px"><span style="font-weight: 400">TLS 1.2</span></td>
<td style="width: 101.344px;height: 58px"><span style="font-weight: 400">Blocked</span></td>
<td style="width: 240.844px;height: 58px"><span style="font-weight: 400">FIPS 140 compliance</span></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">So FUTURE demands a 3072-bit RSA key; otherwise, it is blocked. What do we have?</span><span style="font-weight: 400"><br>
</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">$ openssl rsa -in server-key.pem -text -noout | head -1
RSA Private Key: (2048 bit, 2 primes)</pre>
<p><span style="font-weight: 400">2048 bits! C&rsquo;mon! And now I recall the error log again&hellip; The hint was there:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">error:140AB18F:SSL routines:SSL_CTX_use_certificate:ee key too small</pre>
<p><span style="font-weight: 400">Now we have our story straight.<br>
</span><span style="font-weight: 400">On restart, our PXC cluster node started a new process linked against OpenSSL, which now enforced the FUTURE policy. OpenSSL looked at the 2048-bit RSA certificate and said: &ldquo;Nope. Too small.&rdquo;</span></p>
<h2><span style="font-weight: 400">Fixture</span><a class="anchor-link" id="fixture"></a></h2>
<p><span style="font-weight: 400">The quick fix here would be to adjust the policy to DEFAULT.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">sudo update-crypto-policies --set DEFAULT</pre>
<p><span style="font-weight: 400">This will accept the current SSLs, and the node will join the cluster readily.</span></p>
<p><span style="font-weight: 400">Alternatively, to remain compliant and adhere to the security policy strictness, the fixture will be to</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Generate new certificates</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Deploy the keys/certs to all Galera nodes</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Perform a rolling restart</span></li>
</ul>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">Conclusion</span><a class="anchor-link" id="conclusion"></a></h2>
<p><span style="font-weight: 400">This was a classic case of a problem hiding at the boundary between two domains, database administration and operating system security. The DBA saw valid certificates and correct MySQL configuration. The sysadmin saw a properly hardened system with a strong crypto policy. Neither was wrong. But the intersection of their two correct configurations produced a failure.</span></p>
<p><span style="font-weight: 400">This incident reinforces the importance of cross-domain awareness, where resolving database issues sometimes requires understanding and challenging system-level security decisions.</span></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>The post <a href="https://www.percona.com/blog/curious-case-of-pxc-node-that-refused-to-start-due-to-ssl/">Curious case of PXC node that refused to start due to SSL</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/curious-case-of-pxc-node-that-refused-to-start-due-to-ssl/">Curious case of PXC node that refused to start due to SSL</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Building Query Analysis and Insights Dashboard in PMM</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/building-query-analysis-and-insights-dashboard-in-pmm/" />
      <id>https://www.percona.com/blog/building-query-analysis-and-insights-dashboard-in-pmm/</id>
      <updated>2026-05-04T05:00:52+00:00</updated>
      <author><name>Kedar Vaijanapurkar</name></author>
      <summary type="html"><![CDATA[<p>Percona Monitoring and Management is a great open source database monitoring, observability, and management tool. Query analytics is one of the prominent features DBA uses actively to trace the incidents and query performance identification. We all know and love the Query Analytics (QAN) dashboard… It’s the first place we look when an incident alert fires … Continued<br />
The post Building Query Analysis and Insights Dashboard in PMM appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/building-query-analysis-and-insights-dashboard-in-pmm/">Building Query Analysis and Insights Dashboard in PMM</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">Percona Monitoring and Management is a great open source database monitoring, observability, and management tool. Query analytics is one of the prominent features DBA uses actively to trace the incidents and query performance identification.</span></p>
<p><span style="font-weight: 400">We all know and love the Query Analytics (QAN) dashboard&hellip; It&rsquo;s the first place we look when an incident alert fires or when a developer asks, </span><i><span style="font-weight: 400">&ldquo;Why is the app slow?&rdquo; or &ldquo;What was going on during the midnight production outage?&rdquo;</span></i></p>
<p><span style="font-weight: 400">But sometimes, the standard dashboards just don&rsquo;t tell the whole story or maybe are not clear enough. QAN is great, but shouldn&rsquo;t we have more? If you have PMM running, you already have a Ferrari engine under the hood: ClickHouse. Most of us just drive it in first gear using the default UI.</span></p>
<p><span style="font-weight: 400">In this post, we are going to take the training wheels off. We will bypass the standard QAN interface and talk directly to the ClickHouse backend to build highly specialised dashboards. We aren&rsquo;t just looking for &ldquo;slow&rdquo; queries anymore; we are hunting for inefficiency, volatility, and the &ldquo;silent killers&rdquo; that standard monitoring often misses.</span></p>
<p><span style="font-weight: 400">This is the hands-on blog, so grab your coffee and let&rsquo;s turn that PMM instance into a deep-dive forensic tool.</span></p>
<p><strong>Create a New Dashboard in PMM</strong></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Connect to PMM &gt; Dashboards &gt; Create New Dashboard<img decoding="async" loading="lazy" class="alignnone wp-image-43360" src="https://www.percona.com/wp-content/uploads/2026/04/pmm-new-dashboard.png" alt="" width="172" height="160"></span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Save it with name &ldquo;Slow Query Analysis&rdquo; and Description &ldquo;Slow Query Analysis from PMM&rsquo;s QAN database (clickhouse)&rdquo;</span><img decoding="async" loading="lazy" class="alignnone wp-image-43359" src="https://www.percona.com/wp-content/uploads/2026/04/pmm-save-dashboard.png" alt="" width="681" height="258"></li>
<li style="font-weight: 400"><span style="font-weight: 400">Click on add visualisation &amp; select datasource &ldquo;ClickHouse&rdquo;<br>
<img decoding="async" loading="lazy" class="alignnone size-full wp-image-43358" src="https://www.percona.com/wp-content/uploads/2026/04/pmm-data-source.png" alt="" width="537" height="368"><br>
</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Choose SQL Builder<br>
<img decoding="async" loading="lazy" class="alignnone size-full wp-image-43357" src="https://www.percona.com/wp-content/uploads/2026/04/pmm-sql-builder-1024x574-1.png" alt="" width="1024" height="574"><br>
</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Paste the following query to get top 10 slow queries from the database<br>
</span>
<pre class="urvanov-syntax-highlighter-plain-tag">SELECT fingerprint
    FROM pmm.metrics
    WHERE service_type = 'mysql'
      AND $__timeFilter(period_start)
    GROUP BY fingerprint
    ORDER BY sum(m_query_time_sum) DESC
    LIMIT 10</pre>
</li>
<li style="font-weight: 400"><span style="font-weight: 400">Choose &ldquo;Table View&rdquo; on the top to view the list<br>
</span><span style="font-weight: 400">When you click &ldquo;Run Query&rdquo; you will see the top 10 slow queries in the chosen time period.<img decoding="async" loading="lazy" class="alignnone size-full wp-image-43356" src="https://www.percona.com/wp-content/uploads/2026/04/pmm-table-view-1024x557-1.png" alt="" width="1024" height="557"></span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Let&rsquo;s Save the dashboard after Panel Options updates as follows</span><span style="font-weight: 400">7.1 Change Panel Name and Description to: &ldquo;Slow Query Analysis&rdquo;</span><span style="font-weight: 400">7.2 Legend Placement to &ldquo;Bottom&rdquo;, Values to &ldquo;min&rdquo;,&rdquo;max&rdquo;, &ldquo;mean&rdquo;</span><span style="font-weight: 400">7.3 Change Axis&rsquo; Scale to &ldquo;Logarithmic&rdquo;</span><i><span style="font-weight: 400">Logarithmic scale on an axis compresses large ranges of data, making it ideal for visualizing metrics with vastly different magnitudes. This provides good visualisation for queries of different execution time frames.</span></i><span style="font-weight: 400">7.4 Save Dashboard<img decoding="async" loading="lazy" class="alignnone size-full wp-image-43355" src="https://www.percona.com/wp-content/uploads/2026/04/pmm-dashboard-config.png" alt="" width="401" height="829"></span><span style="font-weight: 400">Alright, we&rsquo;re at our first step. This first result set shows the </span><b>top 10 slow query fingerprints across all MySQL services tracked by PMM</b><span style="font-weight: 400"> for the selected time range. It provides a quick, environment-wide view of the most expensive query patterns. But this does not provide a clear picture. Let&rsquo;s refine the dashboard to focus on specific queries, servers and observe their performance over time.</span><span style="font-weight: 400">Now, let&rsquo;s introduce a variable to filter the data.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Click on </span><b>Settings</b><span style="font-weight: 400"> on Dashboard&rsquo;s home page</span><img decoding="async" loading="lazy" class="alignnone size-full wp-image-43354" src="https://www.percona.com/wp-content/uploads/2026/04/pmm-dashboard-settings.png" alt="" width="634" height="401">8.1 Choose &ldquo;Variables&rdquo; tab and click on &ldquo;Add Variable&rdquo;<img decoding="async" loading="lazy" class="alignnone size-full wp-image-43353" src="https://www.percona.com/wp-content/uploads/2026/04/pmm-add-variables-1024x917-1.png" alt="" width="1024" height="917">8.2 Add variable configuration and Save Dashboard&nbsp;</li>
<li style="font-weight: 400"><span style="font-weight: 400">Go Back to Dashboard and Edit &ldquo;Slow Query Analysis&rdquo; Panel.</span>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Now you should see the Query ID filter on the top.</span></li>
</ul>
</li>
<li style="font-weight: 400"><span style="font-weight: 400">Change the query to the following<br>
</span>
<pre class="urvanov-syntax-highlighter-plain-tag">SELECT
  period_start AS time,
  left(fingerprint, 80) AS query_text,
  sum(m_query_time_sum/m_query_time_cnt) AS query_time
FROM
  pmm.metrics
WHERE
  service_type = 'mysql'
  AND $__timeFilter(period_start)
  AND fingerprint IN (
    SELECT fingerprint
    FROM pmm.metrics
    WHERE service_type = 'mysql'
      AND $__timeFilter(period_start)
      AND ($queryid = '' OR queryid = $queryid)
    GROUP BY fingerprint
    ORDER BY sum(m_query_time_sum) DESC
    LIMIT 10
  )
GROUP BY
  time,
  fingerprint
ORDER BY
  time,
  query_time DESC</pre>

<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Basically the query is fetching start time, query text and average query time for the selected period for the top 10 Queries in that time-frame.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">There is a filter for the &ldquo;queryid&rdquo; variable which you may use if you want to filter on a specific queryid.</span></li>
<li style="font-weight: 400">Choose &ldquo;Time Series&rdquo; as &ldquo;Query Type&rdquo;<img decoding="async" loading="lazy" class="alignnone size-full wp-image-43352" src="https://www.percona.com/wp-content/uploads/2026/04/pmm-adding-query-1024x667-1.png" alt="" width="1024" height="667"></li>
</ul>
</li>
<li style="font-weight: 400"><span style="font-weight: 400">Adjust Panel Options</span><span style="font-weight: 400">11.1 Choose &ldquo;Standard options&rdquo; &gt; &ldquo;Unit&rdquo; as &ldquo;Time / Seconds (s)&rdquo; from drop down.</span><span style="font-weight: 400">11.2 Choose &ldquo;Standard options&rdquo; &gt; &ldquo;Display name&rdquo; as &ldquo;</span><span style="font-weight: 400">${__field.labels.query_text}</span><span style="font-weight: 400">&rdquo;</span><span style="font-weight: 400">11.3 Click on &ldquo;Save Dashboard&rdquo;</span></li>
<li><span style="font-weight: 400"> Your dashboard should be ready<img decoding="async" loading="lazy" class="alignnone size-full wp-image-43351" src="https://www.percona.com/wp-content/uploads/2026/04/pmm-sample-query-chart-1024x549-1.png" alt="" width="1024" height="549"></span></li>
</ol>
<p><span style="font-weight: 400">Now, by default this dashboard is plotting top 10 queries. If you have a query fingerprint handy, you may be able to filter the search by that specific query.&nbsp; That said, this is still plotting queries across all the monitored instances. Let&rsquo;s move on to add the service_name filter.</span></p>
<p>&nbsp;</p>
<h3><b>Adding service_name filter</b><a class="anchor-link" id="adding-service_name-filter"></a></h3>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Add Variable</span>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Create new variable named &ldquo;service_name&rdquo;</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Use variable type &ldquo;Query&rdquo;</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Use Data Source as &ldquo;ClickHouse&rdquo;</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Query:<br>
</span>
<pre class="urvanov-syntax-highlighter-plain-tag">select distinct service_name from pmm.metrics where service_type = 'mysql';</pre>
</li>
<li><span style="font-weight: 400">Unselect all checkboxes in &ldquo;Selection options&rdquo;</span></li>
<li><span style="font-weight: 400">Save Dashboard</span></li>
</ol>
</li>
<li>Update Query</li>
</ol>
<pre class="urvanov-syntax-highlighter-plain-tag">SELECT
  period_start AS time,
  left(fingerprint, 80) AS query_text,
  sum(m_query_time_sum/m_query_time_cnt) AS query_time
FROM
  pmm.metrics
WHERE
  (service_name = '' OR service_name = '$service_name')
  AND service_type = 'mysql'
  AND $__timeFilter(period_start)
  AND fingerprint IN (
    SELECT fingerprint
    FROM pmm.metrics
    WHERE service_type = 'mysql'
      AND $__timeFilter(period_start)
      AND (service_name = '' OR service_name = '$service_name')
    GROUP BY fingerprint
    ORDER BY sum(m_query_time_sum) DESC
    LIMIT 10
  )
GROUP BY
  time,
  left(fingerprint, 80) 
ORDER BY
  time,
  query_time DESC</pre>
<p><span style="font-weight: 400">I know many of you are naturally curious and enjoy experimenting with PMM and Grafana&hellip; So you&rsquo;ve probably already started thinking about how far this can be taken. Feel free to share your ideas or custom dashboards in the comments.</span></p>
<p><span style="font-weight: 400">Sample Dashboards:</span></p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-43350" src="https://www.percona.com/wp-content/uploads/2026/04/pmm-dashboard-service-custom-1024x321-1.png" alt="" width="1024" height="321"> <img decoding="async" loading="lazy" class="alignnone size-full wp-image-43349" src="https://www.percona.com/wp-content/uploads/2026/04/pmm-sample-dashboard-1-1024x456-1.png" alt="" width="1024" height="456"></p>
<h1><span style="font-weight: 400">The Query Analysis and Insights Dashboard</span><a class="anchor-link" id="the-query-analysis-and-insights-dashboard"></a></h1>
<p><span style="font-weight: 400">Okay, for those who are looking to have quick results, I&rsquo;ve prepared the complete Query Analysis and Insights Dashboard for you to import and use instantly.</span></p>
<p><span style="font-weight: 400">By importing the JSON file, you&rsquo;ll get the full working dashboard with all panels preconfigured, including:</span></p>
<ul>
<li><span style="font-weight: 400">Slow Query Analysis</span></li>
<li><span style="font-weight: 400">Latency Distribution Heatmap</span></li>
<li><span style="font-weight: 400">Query Volatility (P99 vs Average)</span></li>
<li><span style="font-weight: 400">Lock Wait Ratio Over Time (Top Contended Queries)</span></li>
<li><span style="font-weight: 400">Temporary Table Usage (Disk &amp; Memory)</span></li>
<li><span style="font-weight: 400">Query Efficiency (Rows Examined vs Rows Sent)</span></li>
<li><span style="font-weight: 400">Error Rate vs Throughput</span></li>
<li><span style="font-weight: 400">Workload Distribution by User</span></li>
<li><span style="font-weight: 400">Query Volume by Client Host</span></li>
<li><span style="font-weight: 400">Execution Time vs Lock Wait Time</span></li>
</ul>
<p><span style="font-weight: 400">This allows you to instantly explore PMM Query Analytics data, adjust time ranges and filters, and correlate query performance, contention, and workload behavior without recreating the dashboard from scratch.</span></p>
<p><span style="font-weight: 400">Dashboard JSON available here:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Grafana: https://grafana.com/grafana/dashboards/24896</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">GitHub:&nbsp; https://github.com/Percona-Lab/pmm-dashboards/query_analysis_insights.json</span></li>
</ul>
<p><span style="font-weight: 400">Give it a go and let me know if you have suggestions or requests. Also consider sharing if you create something interesting.</span></p>
<p>Cheers.</p>
<p>The post <a href="https://www.percona.com/blog/building-query-analysis-and-insights-dashboard-in-pmm/">Building Query Analysis and Insights Dashboard in PMM</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/building-query-analysis-and-insights-dashboard-in-pmm/">Building Query Analysis and Insights Dashboard in PMM</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Adding a New Data Type to MariaDB with Type_handler – Part 2</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-2/" />
      <id>https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-2/</id>
      <updated>2026-05-02T18:12:27+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>After having discovered the Type_hander framework and learned how to build MariaDB Server from source, it’s time to code our first data type! …<br />
Continue reading \"Adding a New Data Type to MariaDB with Type_handler – Part 2\"<br />
The post Adding a New Data Type to MariaDB with Type_handler – Part 2 appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-2/">Adding a New Data Type to MariaDB with Type_handler – Part 2</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>After having <a href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-1/" target="_blank" rel="noreferrer noopener">discovered the Type_hander framework</a> and learned <a href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-0/">how to build MariaDB Server from source</a>, it&rsquo;s time to code our first data type! &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-2/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Adding a New Data Type to MariaDB with Type_handler &ndash; Part 2&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-2/">Adding a New Data Type to MariaDB with Type_handler &ndash; Part 2</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-2/">Adding a New Data Type to MariaDB with Type_handler – Part 2</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>InnoDB Redo Log Sizing: Stop Guessing, Start Measuring</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/05/02/innodb-redo-log-sizing-stop-guessing-start-measuring/" />
      <id>https://percona.community/blog/2026/05/02/innodb-redo-log-sizing-stop-guessing-start-measuring/</id>
      <updated>2026-05-02T00:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>Introduction Many MySQL configurations inherit redo log sizing from defaults, aging blog posts, or configuration folklore.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/02/innodb-redo-log-sizing-stop-guessing-start-measuring/">InnoDB Redo Log Sizing: Stop Guessing, Start Measuring</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<h2 id="introduction">Introduction<a class="anchor-link" id="introduction"></a></h2>
<p>Many MySQL configurations inherit redo log sizing from defaults, aging blog posts, or configuration folklore.</p>
<p><code>innodb_redo_log_capacity</code> gets set once&hellip; and then quietly fades into the background.</p>
<p>But redo log capacity directly shapes how efficiently MySQL absorbs writes, manages checkpoint pressure, and handles burst-heavy workloads.</p>
<p>Set it too low, and aggressive flushing can throttle throughput.<br>
Set it too high, and crash recovery can become painfully long.</p>
<p>Redo logs are more than crash insurance.</p>
<p>They are part of your write-performance architecture.</p>
<blockquote>
<p>Redo logs are the shock absorbers of write-heavy MySQL. Too small, and performance jolts. Too large, and recovery drags.</p>
</blockquote>
<h2 id="why-redo-logs-matter">Why Redo Logs Matter<a class="anchor-link" id="why-redo-logs-matter"></a></h2>
<p>InnoDB redo logs are often described as crash recovery journals, but that description undersells their real operational value.</p>
<p>Redo logs function as a write buffer between committed transactions and eventual data file writes.</p>
<p>When a transaction commits:</p>
<ul>
<li>Changes are written to the redo log first</li>
<li>Dirty pages remain in memory</li>
<li>Data pages are flushed later</li>
</ul>
<p>This write-ahead logging (WAL) design allows MySQL to:</p>
<ul>
<li>Absorb bursts of write activity</li>
<li>Reduce immediate random disk writes</li>
<li>Smooth checkpoint behavior</li>
<li>Preserve durability</li>
</ul>
<p>Redo logs act like pressure regulators in a write-heavy system.</p>
<p>They absorb pressure spikes so the entire system doesn&rsquo;t thrash every time demand increases.</p>
<p>Without enough redo capacity, MySQL has less room to absorb write bursts before it must flush aggressively.</p>
<h2 id="checkpoint-age-and-flushing-pressure">Checkpoint Age and Flushing Pressure<a class="anchor-link" id="checkpoint-age-and-flushing-pressure"></a></h2>
<p>Redo log sizing becomes most visible when checkpoint pressure builds.</p>
<p>Checkpoint age represents how far current write activity has advanced beyond the last durable checkpoint:</p>
<p><code>Checkpoint Age = Current LSN - Last Checkpoint LSN</code></p>
<p>As checkpoint age approaches total redo capacity:</p>
<ul>
<li>Adaptive flushing intensifies</li>
<li>Page cleaners become more aggressive</li>
<li>Dirty pages flush faster</li>
<li>Disk I/O spikes</li>
<li>Latency often becomes unstable</li>
</ul>
<p>This is where undersized redo logs can trigger flush storms.</p>
<blockquote>
<p>MySQL isn&rsquo;t writing more data. It&rsquo;s being forced to write sooner and less efficiently.</p>
</blockquote>
<h3 id="useful-metrics">Useful metrics<a class="anchor-link" id="useful-metrics"></a></h3>
<ul>
<li><code>Innodb_checkpoint_age</code></li>
<li><code>Innodb_buffer_pool_pages_dirty</code></li>
<li><code>Innodb_data_fsyncs</code></li>
<li><code>Innodb_log_waits</code></li>
</ul>
<blockquote>
<p>When redo space shrinks, MySQL doesn&rsquo;t stop writing. It starts panicking earlier.</p>
</blockquote>
<h2 id="symptoms-of-undersized-redo">Symptoms of Undersized Redo<a class="anchor-link" id="symptoms-of-undersized-redo"></a></h2>
<p>Small redo logs rarely announce themselves directly.</p>
<p>Instead, they often masquerade as generalized storage or write-performance issues.</p>
<h3 id="common-warning-signs">Common warning signs<a class="anchor-link" id="common-warning-signs"></a></h3>
<ul>
<li>Periodic write stalls</li>
<li>Spikes in fsync activity</li>
<li>Sharp increases in page cleaner workload</li>
<li>TPS drops during burst traffic</li>
<li>Dirty page percentage volatility</li>
<li>Stable CPU, unstable write latency</li>
</ul>
<h3 id="a-common-misdiagnosis">A common misdiagnosis<a class="anchor-link" id="a-common-misdiagnosis"></a></h3>
<p>Many systems blame disks when the real issue is insufficient redo headroom.</p>
<p>If writes are arriving faster than redo can comfortably buffer them, MySQL is forced into reactive flushing patterns.</p>
<p>The problem may not be disk speed.</p>
<p>It may be timing pressure.</p>
<h2 id="measuring-with-status-counters">Measuring with Status Counters<a class="anchor-link" id="measuring-with-status-counters"></a></h2>
<p>Redo log sizing should be based on observed workload, not memory percentages or inherited defaults.</p>
<h3 id="step-1-measure-redo-generation-rate">Step 1: Measure redo generation rate<a class="anchor-link" id="step-1-measure-redo-generation-rate"></a></h3>
<p>Use:</p>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">sql</span><button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-0">
<div class="highlight">
<pre class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SHOW</span><span class="w"> </span><span class="n">ENGINE</span><span class="w"> </span><span class="n">INNODB</span><span class="w"> </span><span class="n">STATUS</span><span class="p">;</span></span></span></code></pre>
</div>
</div>
</div>
<p>Track:</p>
<ul>
<li>Log sequence number</li>
<li>Log flushed up to</li>
<li>Last checkpoint at</li>
</ul>
<p>Measure LSN growth over time:</p>
<p><code>Redo Generation Rate = (LSN delta) / elapsed time</code></p>
<h3 id="example">Example<a class="anchor-link" id="example"></a></h3>
<p>If LSN grows by 4 GB over one hour:</p>
<ul>
<li>1 GB redo capacity = frequent pressure</li>
<li>4 GB redo capacity = ~1 hour buffer</li>
<li>8 GB redo capacity = larger burst tolerance</li>
</ul>
<h3 id="step-2-watch-for-log-stress">Step 2: Watch for log stress<a class="anchor-link" id="step-2-watch-for-log-stress"></a></h3>
<div class="code-block">
<div class="code-block__header"><span class="code-block__lang">sql</span><button class="code-block__copy" type="button" data-copy-target="codeblock-1" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-1">
<div class="highlight">
<pre class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SHOW</span><span class="w"> </span><span class="k">GLOBAL</span><span class="w"> </span><span class="n">STATUS</span><span class="w"> </span><span class="k">LIKE</span><span class="w"> </span><span class="s1">'Innodb_log_waits'</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">SHOW</span><span class="w"> </span><span class="k">GLOBAL</span><span class="w"> </span><span class="n">STATUS</span><span class="w"> </span><span class="k">LIKE</span><span class="w"> </span><span class="s1">'Innodb_os_log%'</span><span class="p">;</span></span></span></code></pre>
</div>
</div>
</div>
<h3 id="key-metric">Key metric<a class="anchor-link" id="key-metric"></a></h3>
<p><code>Innodb_log_waits</code></p>
<p>If this value increases, transactions are waiting for log free space.</p>
<p>That is one of the clearest signs your redo logs may be too small.</p>
<p><code>Innodb_log_waits</code> is less a tuning suggestion and more a smoke alarm.</p>
<h2 id="practical-sizing-strategy">Practical Sizing Strategy<a class="anchor-link" id="practical-sizing-strategy"></a></h2>
<p>Forget percentage-of-RAM formulas.</p>
<p>Redo logs should be sized around workload intensity.</p>
<p><strong>A practical starting point:</strong></p>
<p>Size redo capacity to hold 30 to 60 minutes of peak redo generation</p>
<h3 id="example-1">Example<a class="anchor-link" id="example"></a></h3>
<p>Peak redo generation = 6 GB/hour</p>
<p><strong>Minimum:</strong></p>
<p>30 minutes = 3 GB</p>
<p><strong>Safer:</strong></p>
<p>60 minutes = 6 GB</p>
<p><strong>Heavy burst environments:</strong></p>
<p>Larger sizing may reduce flush volatility further</p>
<h2 id="trade-offs">Trade-Offs<a class="anchor-link" id="trade-offs"></a></h2>
<h3 id="smaller-redo-logs">Smaller Redo Logs<a class="anchor-link" id="smaller-redo-logs"></a></h3>
<p><strong>Pros:</strong></p>
<ul>
<li>Faster crash recovery</li>
<li>Lower storage footprint</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>Increased checkpoint pressure</li>
<li>More aggressive flushing</li>
<li>Greater write instability</li>
</ul>
<h3 id="larger-redo-logs">Larger Redo Logs<a class="anchor-link" id="larger-redo-logs"></a></h3>
<p><strong>Pros:</strong></p>
<ul>
<li>Better burst absorption</li>
<li>Smoother sustained write performance</li>
<li>Reduced flush storms</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>Longer crash recovery</li>
<li>Delayed visibility into pressure buildup</li>
</ul>
<h2 id="common-mistakes">Common Mistakes<a class="anchor-link" id="common-mistakes"></a></h2>
<ol>
<li>
<p><strong>Treating redo like buffer pool sizing</strong><br>
Redo capacity is about write throughput buffering, not memory caching.</p>
</li>
<li>
<p><strong>Ignoring Innodb_log_waits</strong><br>
This can leave obvious pressure invisible until performance suffers.</p>
</li>
<li>
<p><strong>Oversizing without testing recovery</strong><br>
Large redo logs may improve runtime but worsen restart scenarios.</p>
</li>
<li>
<p><strong>Sizing for average load instead of peak</strong><br>
Redo logs exist to absorb pressure spikes, not calm periods.</p>
</li>
</ol>
<h2 id="final-thoughts">Final Thoughts<a class="anchor-link" id="final-thoughts"></a></h2>
<p>The right redo log size isn&rsquo;t about maximizing a configuration value.</p>
<p>It&rsquo;s about matching capacity to workload behavior.</p>
<ul>
<li>Too small, and MySQL becomes reactive.</li>
<li>Too large, and crash recovery becomes the hidden tax.</li>
</ul>
<p>When redo logs are properly sized, they fade into the background.</p>
<p>They quietly absorb bursts, smooth checkpoint behavior, and preserve performance consistency under pressure.</p>
<blockquote>
<p>Redo logs work best when they disappear into the background, quietly absorbing pressure instead of creating it.</p>
</blockquote>
<p>Stop guessing.</p>
<p>Measure your workload, observe your log pressure, and size with intent.</p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/05/02/innodb-redo-log-sizing-stop-guessing-start-measuring/">InnoDB Redo Log Sizing: Stop Guessing, Start Measuring</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Run an ALTER TABLE for a huge table in Aurora</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/run-an-alter-table-for-a-huge-table-in-aurora/" />
      <id>https://www.percona.com/blog/run-an-alter-table-for-a-huge-table-in-aurora/</id>
      <updated>2026-05-01T01:55:40+00:00</updated>
      <author><name>Eduardo Krieg</name></author>
      <summary type="html"><![CDATA[<p>Recently, we received an alert for one of our Managed Services customers indicating that the auto_increment value for the table was 80% of its maximum capacity. The column was INT UNSIGNED, which has a limit of 4,294,967,295. At 80%, we have enough time to change it to BIGINT.…. Right? Let’s see. So we used pt-online-schema-change … Continued<br />
The post Run an ALTER TABLE for a huge table in Aurora appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/run-an-alter-table-for-a-huge-table-in-aurora/">Run an ALTER TABLE for a huge table in Aurora</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">Recently, we received an alert for one of our </span><a href="https://www.percona.com/services/managed-services"><span style="font-weight: 400">Managed Services</span></a><span style="font-weight: 400"> customers indicating that the auto_increment value for the table was 80% of its maximum capacity. The column was INT UNSIGNED, which has a limit of 4,294,967,295.</span></p>
<p><span style="font-weight: 400">At 80%, we have enough time to change it to BIGINT.&hellip;. Right? Let&rsquo;s see.</span></p>
<p><span style="font-weight: 400">So we used </span><a href="https://docs.percona.com/percona-toolkit/pt-online-schema-change.html"><span style="font-weight: 400">pt-online-schema-change</span></a><span style="font-weight: 400"> to perform the alter.</span></p>
<p><span style="font-weight: 400">It started running at a good pace but slowed over time.</span></p>
<p>&nbsp;</p>
<p><b>Why?</b></p>
<p><span style="font-weight: 400">Well, let&rsquo;s look at the definition of the table:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; show create table myschema.mytableG
*************************** 1. row ***************************
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Table: mytable
Create Table: CREATE TABLE `mytable` (
&nbsp;&nbsp;`id` int unsigned NOT NULL AUTO_INCREMENT,
&nbsp;&nbsp;`long_column` varchar(1000) NOT NULL,
&nbsp;&nbsp;`state` tinyint unsigned NOT NULL,
&nbsp;&nbsp;`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
&nbsp;&nbsp;`short_column` varchar(30) NOT NULL,
&nbsp;&nbsp;PRIMARY KEY (`id`),
&nbsp;&nbsp;KEY `idx_long_column` (`long_column`,`state`),
&nbsp;&nbsp;KEY `idx_short_column` (`short_column`,`state`),
&nbsp;&nbsp;KEY `idx_short_col2` (`short_column`)
) ENGINE=InnoDB AUTO_INCREMENT=4009973818 DEFAULT CHARSET=utf8mb3</pre>
<p><b>NOTE</b><b>1</b><b>: </b><span style="font-weight: 400">The index on </span><i><span style="font-weight: 400">long_column</span></i><span style="font-weight: 400"> is for a varchar column with a length of 1000; it may not be required, and an index prefix may be more helpful here.</span></p>
<p><b>NOTE</b><b>2</b><b>:</b><span style="font-weight: 400"> The index </span><i><span style="font-weight: 400">idx_short_col2</span></i><span style="font-weight: 400"> is duplicated, as it is covered by the index </span><i><span style="font-weight: 400">idx_short_column</span></i><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">Those changes require testing and are out of scope for this emergency, but they are worth mentioning.</span></p>
<p>&nbsp;</p>
<p><strong>Table size:</strong><span style="font-weight: 400"><br>
</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">+---------------+------------+------------+---------+----------+---------+----------+--------+
| TABLE_SCHEMA&nbsp; | TABLE_NAME | TABLE_ROWS | DATA_GB | INDEX_GB | FREE_GB | TOTAL_GB | ENGINE |
+---------------+------------+------------+---------+----------+---------+----------+--------+
| myschema      | mytable    | 3906921584 |&nbsp; &nbsp; 1118 | &nbsp; &nbsp; 1790 | &nbsp; &nbsp; &nbsp; 0 | &nbsp; &nbsp; 2907 | InnoDB |
+---------------+------------+------------+---------+----------+---------+----------+--------+</pre>
<p><span style="font-weight: 400">Look at the indexes being way bigger than the data.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; SELECT database_name, table_name, index_name, ROUND(stat_value * @@innodb_page_size / 1024 / 1024, 2) AS size_in_mb FROM mysql.innodb_index_stats WHERE stat_name = 'size' AND index_name != 'PRIMARY' and database_name='myschema' and table_name='mytable' ORDER BY size_in_mb DESC;
+---------------+------------+-------------------+------------+
| database_name | table_name | index_name&nbsp; &nbsp; &nbsp; &nbsp; | size_in_mb |
+---------------+------------+-------------------+------------+
| myschema&nbsp; &nbsp; &nbsp; | mytable&nbsp; &nbsp; | idx_long_column &nbsp; | 1583538.95 |
| myschema&nbsp; &nbsp; &nbsp; | mytable&nbsp; &nbsp; | idx_short_column&nbsp; |&nbsp; 126432.98 |
| myschema&nbsp; &nbsp; &nbsp; | mytable&nbsp; &nbsp; | idx_short_col2&nbsp; &nbsp; |&nbsp; 122699.95 |
+---------------+------------+-------------------+------------+
3 rows in set (0.01 sec)</pre>
<p><span style="font-weight: 400">While the pt-online-schema-change runs, it copies the data to a new table. As the data is being copied, the secondary indexes must be maintained.</span></p>
<p><span style="font-weight: 400">NOTE the huge index for a varchar(1000) that is ~1.5T in size.&nbsp;</span><span style="font-weight: 400">Maintaining such an index becomes increasingly expensive as the data size increases.</span></p>
<p><span style="font-weight: 400">The pt-online-schema-change had been running for ~8 days, and its latest estimate was 53 more days, which we can&rsquo;t afford, since the maximum value would be exceeded in ~15 days.&nbsp;</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">Copying `myschema`.`mytable`:&nbsp; 12% 53+16:48:01 remain
Copying `myschema`.`mytable`:&nbsp; 12% 53+16:48:30 remain
Copying `myschema`.`mytable`:&nbsp; 12% 53+16:48:59 remain
Copying `myschema`.`mytable`:&nbsp; 12% 53+16:49:26 remain
Copying `myschema`.`mytable`:&nbsp; 12% 53+16:49:53 remain
Copying `myschema`.`mytable`:&nbsp; 12% 53+16:50:19 remain
Copying `myschema`.`mytable`:&nbsp; 12% 53+16:50:49 remain
Copying `myschema`.`mytable`:&nbsp; 12% 53+16:51:17 remain
Copying `myschema`.`mytable`:&nbsp; 12% 53+16:51:45 remain</pre>
<p>&nbsp;</p>
<p><span style="font-weight: 400">So what do we do now?</span></p>
<p><span style="font-weight: 400">We suggested canceling the pt-online-schema-change and creating an Aurora </span><a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/blue-green-deployments-overview.html"><span style="font-weight: 400">blue-green deployment</span></a><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">Then perform the direct ALTER on the green cluster. And finally, when ready, do the failover.</span></p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">Sounds good, doesn&rsquo;t it?</span></p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">First, we need to ensure that the new cluster (green) has the </span><a href="https://dev.mysql.com/doc/refman/8.4/en/replication-options-replica.html#sysvar_replica_type_conversions"><b>replica_type_conversions</b></a><span style="font-weight: 400">&nbsp; parameter in its cluster parameter group to &ldquo;</span><i><span style="font-weight: 400">ALL_NON_LOSSY, ALL_UNSIGNED&rdquo;</span></i><span style="font-weight: 400"> in order to be able to replicate from an int unsigned column to a bigint unsigned column.</span></p>
<p><span style="font-weight: 400">So we tried that, it started too fast ~0.036% per minute, that&rsquo;s 2 days. That&rsquo;s great!</span></p>
<p><span style="font-weight: 400">We left the process running over the weekend, but we noticed it started to slow down again&hellip; By Monday, it was advancing at ~0.01% every 5 mins, which gives an ETA of 34 days.&nbsp;</span></p>
<p><strong>Why?&nbsp;</strong></p>
<p><span style="font-weight: 400">Again, using the direct ALTER MySQL copies the data to a temp table, and the bigger the data, the harder it is to maintain the indexes.&nbsp;</span></p>
<p><span style="font-weight: 400">Again, unacceptable.</span></p>
<p><span style="font-weight: 400">Note that with the above 2 approaches, we lost ~12 days of precious time, and the deadline for auto_increment exhaustion was approaching.</span></p>
<p><span style="font-weight: 400">Then we thought: What if we drop the secondary indexes, do the alter, and then add the indexes back?</span></p>
<p><span style="font-weight: 400">In theory, it should be faster, as:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Dropping the indexes is a metadata-only operation with </span><a href="https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-operations.html"><span style="font-weight: 400">ONLINE DDL</span></a><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Altering the column datatype from INT to BIGINT is not an ONLINE operation, but the fact that it doesn&rsquo;t have to update secondary indexes during row copying to a new temporary table prevents the slowdown.</span><span style="font-weight: 400"><br>
</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Adding back the secondary indexes is an ONLINE DDL operation:</span></li>
</ul>
<p>&nbsp;</p>
<p><span style="font-weight: 400">&ldquo;Online DDL support for adding secondary indexes means that you can generally speed the overall process of creating and loading a table and associated indexes by creating the table without secondary indexes, then adding secondary indexes after the data is loaded.&rdquo;</span></p>
<p><a href="https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-operations.html"><span style="font-weight: 400">https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-operations.html</span></a></p>
<p><span style="font-weight: 400">So let&rsquo;s do this:</span></p>
<p><span style="font-weight: 400">The deletion of the indexes was really quick, as expected (metadata-only operation):</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; ALTER TABLE myschema.mytable DROP INDEX idx_long_column, DROP INDEX idx_short_column, DROP INDEX idx_short_col2;
Query OK, 0 rows affected (49.40 sec)
Records: 0&nbsp; Duplicates: 0&nbsp; Warnings: 0</pre>
<p>&nbsp;</p>
<p><span style="font-weight: 400">Then the change of the datatype:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; ALTER TABLE myschema.mytable CHANGE COLUMN id id bigint unsigned NOT NULL AUTO_INCREMENT;
Query OK, 4058047205 rows affected (13 hours 9 min 10.62 sec)
Records: 4058047205&nbsp; Duplicates: 0&nbsp; Warnings: 0</pre>
<p>&nbsp;</p>
<p><span style="font-weight: 400">Looks very promising!!!</span></p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">The final step, add back the indexes:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; ALTER TABLE myschema.mytable ADD INDEX `idx_long_column` (`long_column`,`state`), ADD INDEX `idx_short_column` (`short_column`,`state`), ADD INDEX `short_col2` (`short_column`);
ERROR 1878 (HY000): Temporary file write failure.</pre>
<p>&nbsp;</p>
<p><b>Why?</b></p>
<p><span style="font-weight: 400">Well, the INPLACE operation uses the tmp dir to write sort files. In Aurora, there are </span><a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Managing.Performance.html#AuroraMySQL.Managing.TempStorage"><span style="font-weight: 400">certain limits for the temporary space based on the instance type</span></a><span style="font-weight: 400">.&nbsp;</span></p>
<p>In a regular MySQL instance, we can modify the <strong>innodb_tmpdir</strong> to another location with enough disk space; however, in Aurora, the parameter is not modifiable, which could have made the whole process easier.</p>
<p><span style="font-weight: 400">Even with a larger instance type, it&rsquo;s hard to create the 1.5T index without breaking open the piggy bank.</span></p>
<p>&nbsp;</p>
<p><b><i>Last resort</i></b><span style="font-weight: 400">, add the indexes back with the COPY algorithm:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; ALTER TABLE myschema.mytable ALGORITHM=COPY, ADD INDEX `idx_long_column` (`long_column`,`state`), ADD INDEX `idx_short_column` (`short_column`,`state`), ADD INDEX `idx_short_col2` (`short_column`);
Query OK, 4147498819 rows affected (6 days 1 hour 55 min 57.00 sec)
<span style="font-weight: 400">Records: 4147498819&nbsp; Duplicates: 0&nbsp; Warnings: 0</span></pre>
<p>&nbsp;</p>
<p><span style="font-weight: 400">Why does it work? Because ALTER TABLE using the COPY algorithm uses the datadir as the destination for the temporary table, the rows are copied there. It doesn&rsquo;t have the limitation of the temporary directory mentioned above.</span></p>
<p><span style="font-weight: 400">We were able to make it on time about 4 days before the auto_increment exhaustion, preventing downtime.</span></p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">In retrospective we could have used the following approach to avoid the use of the blue/green deployment:</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Perform a pt-online-schema-change on the main table, dropping the indexes, and changing the column type to bigint. ( with &ndash;no-swap-tables &ndash;no-drop-old-table &ndash;no-drop-new-table &ndash;no-drop-triggers).</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Add the secondary indexes using the direct alter with the COPY algorithm in the _new table.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Once the alter finishes, swap the tables and drop the triggers.</span></li>
</ol>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">Conclusion:</span><a class="anchor-link" id="conclusion"></a></h2>
<p><span style="font-weight: 400">What initially looked like an easy task with </span><a href="https://docs.percona.com/percona-toolkit/pt-online-schema-change.html"><span style="font-weight: 400">pt-online-schema-change</span></a><span style="font-weight: 400">, ended up being more complex.&nbsp;</span></p>
<p><span style="font-weight: 400">You need to check the data definition, the index sizes, the Aurora limits, and how the different algorithms work to make a decision on the best way to proceed with those tasks, specially on situations like these where you have the pressure of the auto_increment being exhausted and there&rsquo;s risk of downtime if it is not done on time.</span></p>
<p><span style="font-weight: 400">And of course, monitor auto_increment exhaustion for your tables, and use a reasonable threshold that gives you enough time to plan and change the table definition. You can use </span><a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management"><span style="font-weight: 400">Percona Monitoring and Management</span></a><span style="font-weight: 400"> for this, specifically on the MySQL &gt; MySQL Table Details dashboard.</span></p>
<p>The post <a href="https://www.percona.com/blog/run-an-alter-table-for-a-huge-table-in-aurora/">&lt;H1&gt; Run an ALTER TABLE for a huge table in Aurora</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/run-an-alter-table-for-a-huge-table-in-aurora/">Run an ALTER TABLE for a huge table in Aurora</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Managing Valkey Cluster in Kubernetes</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/managing-valkey-cluster-in-kubernetes/" />
      <id>https://www.percona.com/blog/managing-valkey-cluster-in-kubernetes/</id>
      <updated>2026-04-30T23:05:39+00:00</updated>
      <author><name>Matthew Boehm</name></author>
      <summary type="html"><![CDATA[<p>Over the last several years, Percona has introduced several rock-star Kubernetes Operators for managing MySQL, Percona XtraDB Cluster, MongoDB, and PostgreSQL. For Valkey, we are actively working with the community to contribute our knowledge, and experience to help brainstorm, develop, and test the official Valkey Operator for Kubernetes. While the Valkey Operator has not yet … Continued<br />
The post Managing Valkey Cluster in Kubernetes appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/managing-valkey-cluster-in-kubernetes/">Managing Valkey Cluster in Kubernetes</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">Over the last several years, Percona has introduced several rock-star </span><a href="https://docs.percona.com/percona-operators"><span style="font-weight: 400">Kubernetes Operators</span></a><span style="font-weight: 400"> for managing MySQL, Percona XtraDB Cluster, MongoDB, and PostgreSQL. For Valkey, we are actively working with the community to contribute our knowledge, and experience to help brainstorm, develop, and test the official </span><a href="https://github.com/valkey-io/valkey-operator/"><span style="font-weight: 400">Valkey Operator for Kubernetes</span></a><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">While the <a href="https://github.com/valkey-io/valkey-operator">Valkey Operator</a> has not yet released a GA 1.0 version, we wanted to take this opportunity to highlight some recently added features.</span></p>
<h2><span style="font-weight: 400">Cluster Configuration</span><a class="anchor-link" id="cluster-configuration"></a></h2>
<p><span style="font-weight: 400">Up until recently, there was no native ability to provide configuration parameters to the Valkey server process running inside each deployed pod. This hurdle is now overcome, and you can supply configuration natively within the deployment CR.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: valkey.io/v1alpha1
kind: ValkeyCluster
metadata:
&nbsp;&nbsp;name: my-valkey-cluster1
spec:
&nbsp;&nbsp;shards: 3
&nbsp;&nbsp;replicas: 1
&nbsp;&nbsp;config:
&nbsp;&nbsp;&nbsp;&nbsp;maxmemory: 500mb
&nbsp;&nbsp;&nbsp;&nbsp;maxmemory-policy: allkeys-lfu
&nbsp;&nbsp;&nbsp;&nbsp;maxclients: 5000
&nbsp;&nbsp;&nbsp;&nbsp;commandlog-execution-slower-than: 10000</pre>
<p><span style="font-weight: 400">For now, these parameters are set on initial cluster deployment. There is already </span><a href="https://github.com/valkey-io/valkey-operator/issues/141"><span style="font-weight: 400">traction underway</span></a><span style="font-weight: 400"> to allow certain parameters to be dynamically set at runtime. There are a small handful of certain cluster-based parameters that cannot be overridden by the user, otherwise it would break operator functionality.</span></p>
<h2><span style="font-weight: 400">User Access Control List (ACL)</span><a class="anchor-link" id="user-access-control-list-acl"></a></h2>
<p><span style="font-weight: 400">Managing users is always a tedious task for any database administrator. Creating ACLs for users in Valkey can be a bit confusing coming from a traditional RDBMS using GRANT syntax. To make things just a bit easier, Valkey Operator has added user permissions management to the deployment CR.</span></p>
<p><span style="font-weight: 400">Firstly, create your Secret containing usernames, and passwords:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: v1
kind: Secret
metadata:
&nbsp;&nbsp;name: valkey-cluster-sample-users
data:
&nbsp;&nbsp;alicepw: M21wdHlQQHNzdzByZA==
&nbsp;&nbsp;davidold: OVYqTHQlYXU4Mk5tdTlyeQ==
&nbsp;&nbsp;davidnew: VmFsa2V5I1J1bHojMjIzMw==</pre>
<p><span style="font-weight: 400">Next, deploy your cluster with users:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: valkey.io/v1alpha1
kind: ValkeyCluster
metadata:
&nbsp;&nbsp;name: my-cool-valkey-cluster
spec:
&nbsp;&nbsp;shards: 3
&nbsp;&nbsp;replicas: 1
&nbsp;&nbsp;users:
&nbsp;&nbsp;&nbsp;&nbsp;- name: alice
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;enabled: true
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;passwordSecret:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;name: valkey-cluster-sample-users
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;keys: [alicepw]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;commands:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;allow: ["@read", "@write", "@connection"]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;deny: ["@admin", "@dangerous"]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;keys:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;readWrite: ["app:*", "cache:*"]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;readOnly: ["shared:*", "config:*"]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;writeOnly: ["logs:*", "metrics:*"]
&nbsp;&nbsp;&nbsp;&nbsp;- name: david
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;enabled: true
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;passwordSecret:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;name: valkey-cluster-sample-users
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;keys:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- davidold
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- davidnew
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;commands:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;allow: ["@admin"]</pre>
<p><span style="font-weight: 400">There&rsquo;s quite a lot going on here. Let&rsquo;s break it down by first looking at the user &lsquo;alice&rsquo;:&nbsp;</span></p>
<p><span style="font-weight: 400">The &lsquo;alice&rsquo; user is enabled, with a password found in the referenced Secret and secret key. Next, we can see what commands, or in this case, command groups (Noted with &lsquo;@&rsquo;) that alice is allowed to execute, and which commands/groups are denied. Lastly, permissions on specific key patterns are identified for maximum security restrictions.</span></p>
<p><span style="font-weight: 400">The other user, &lsquo;david&rsquo;, can access all of the admin-group commands, and cannot read or write to any keys. Note that david&rsquo;s secret key reference is an array, which means you can provide multiple passwords per user; great for password rotation! Once david confirms the new password, the old password references can be removed from the CR and Secret, and the Valkey Operator will synchronize the ACLs.</span></p>
<p><span style="font-weight: 400">Users are dynamic, which means they can be added, removed, and modified without restarting the cluster.</span></p>
<h2><span style="font-weight: 400">TLS Support</span><a class="anchor-link" id="tls-support"></a></h2>
<p><span style="font-weight: 400">Bring on the encryption! TLS support was also recently added to the Valkey Operator. Create your Secret with the CA, TLS Key, and Cert files, and tell the CR where to find them:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: valkey.io/v1alpha1
kind: ValkeyCluster
metadata:
&nbsp;&nbsp;name: cluster-sample
spec:
&nbsp;&nbsp;shards: 3
&nbsp;&nbsp;replicas: 1
&nbsp;&nbsp;tls:
&nbsp;&nbsp;&nbsp;&nbsp;certificate:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;secretName: my-valkey-tls-secret</pre>
<p><span style="font-weight: 400">Once deployed, the Valkey operator will mount the referenced secret to each pod, and add all the proper configuration parameters. By doing so, the operator enforces SSL/TLS communication between each Valkey cluster node, securing node-to-node, and replication traffic within your kubernetes network. Additionally, by creating user certificates signed by the same CA, traffic between your clients, and the Valkey clusters nodes is secured. This configuration is BYOC (bring-your-own-certificate), which works well with the popular CertManager, or other certificate authority you may be using.</span></p>
<h2><span style="font-weight: 400">On The Horizon</span><a class="anchor-link" id="on-the-horizon"></a></h2>
<p><span style="font-weight: 400">As a teaser, here are a couple other features coming soon to Valkey Operator:</span></p>
<ul>
<li style="font-weight: 400"><a href="https://valkey.io/topics/persistence/"><span style="font-weight: 400">Data Persistence</span></a><span style="font-weight: 400">: The ability to enable background snapshots of the in-memory dataset for backup, and recovery. Additionally, supporting the AOF (append-only file) for streaming changes.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Simple Replication: The operator currently only supports Valkey in </span><a href="https://valkey.io/topics/cluster-tutorial/"><span style="font-weight: 400">cluster mode</span></a><span style="font-weight: 400">. Be on the lookout for traditional primary -&gt; N-replica configurations, along with Sentinel monitoring.</span></li>
</ul>
<h2><span style="font-weight: 400">Join Us</span><a class="anchor-link" id="join-us"></a></h2>
<p><span style="font-weight: 400">Want to contribute to the Valkey Operator? Join any of the discussions/</span><a href="https://github.com/valkey-io/valkey-operator/issues"><span style="font-weight: 400">issues</span></a><span style="font-weight: 400"> on our github, or come introduce yourself in the </span><a href="https://valkey.io/slack"><span style="font-weight: 400">Valkey Slack</span></a><span style="font-weight: 400"> community.</span></p>
<p>The post <a href="https://www.percona.com/blog/managing-valkey-cluster-in-kubernetes/">Managing Valkey Cluster in Kubernetes</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/managing-valkey-cluster-in-kubernetes/">Managing Valkey Cluster in Kubernetes</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Open source doesn’t die. It gets unfunded.</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/04/30/open-source-doesnt-die.-it-gets-unfunded./" />
      <id>https://percona.community/blog/2026/04/30/open-source-doesnt-die.-it-gets-unfunded./</id>
      <updated>2026-04-30T11:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>If you are using PostgreSQL in any capacity very likely this week has started for you with a bang. pgBackRest, one of the most known tools for PostgreSQL, praised for the scalable and reliable way to do backups has announced that the project is currently archived.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/04/30/open-source-doesnt-die.-it-gets-unfunded./">Open source doesn’t die. It gets unfunded.</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>If you are using PostgreSQL in any capacity very likely this week has started for you with a bang. pgBackRest, one of the most known tools for PostgreSQL, praised for the scalable and reliable way to do backups has announced that the project is currently archived.</p>
<h2 id="archived-you-mean-eol">Archived, <a href="https://www.reddit.com/r/PostgreSQL/comments/1sx2ttg/comment/oilzdag/?utm_source=share&amp;utm_medium=web3x&amp;utm_name=web3xcss&amp;utm_term=1&amp;utm_content=share_button" target="_blank" rel="noopener noreferrer">you mean EOL</a>?<a class="anchor-link" id="archived-you-mean-eol"></a></h2>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/04/opensourcedoesntdie-reddit.png" alt="blog/2026/04/opensourcedoesntdie-reddit.png"></figure>
</p>
<p>No! Open source software rarely has a hard &ldquo;end of life.&rdquo; What it does have are maintainership gaps and those can be just as serious.</p>
<p>It&rsquo;s different when PostgreSQL community announces a major version EOL. This happens because Community chooses to not to support it and move on to focus on newer versions.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/04/opensourcedoesntdie-thisisopensource.png" alt="blog/2026/04/opensourcedoesntdie-thisisopensource.png"></figure>
</p>
<p>Reading the message from David Steele, the long-time primary maintainer of pgBackRest you will not find &ldquo;end of life&rdquo; term. The project is marked read-only and no longer actively maintained, but that is not the same as being permanently dead.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/04/opensourcedoesntdie-maintenance.png" alt="blog/2026/04/opensourcedoesntdie-maintenance.png"></figure>
</p>
<p>pgBackRest is not &ldquo;end of life.&rdquo; There is no governing body declaring support ended. What happened is simpler and more common in open source: the maintainer can no longer afford to continue.</p>
<h2 id="so-what-happened-then">So what happened then?<a class="anchor-link" id="so-what-happened-then"></a></h2>
<p>This requires some story telling and I don&rsquo;t think that I can do it better than <a href="https://mydbanotebook.org/about/" target="_blank" rel="noopener noreferrer">L&aelig;titia Avrot</a> already did in her <a href="https://mydbanotebook.org/posts/pgbackrest-is-dead.-now-what/#what-happened" target="_blank" rel="noopener noreferrer">blogpost</a> (though I do not like the title):</p>
<blockquote>
<p>Crunchy Data, which had sponsored&nbsp;<code>pgBackRest</code>&nbsp;for most of its life and employed David, was sold. After that, David spent months looking for a position that would let him keep working on the project. He also tried to secure independent sponsorship. Neither worked out. He needs to make a living. The project requires sustained effort which he can no longer provide without being paid for it.</p>
</blockquote>
<p>This is the issue. An experienced developer, who wants to work on the project (that a big chunk of enterprises use) finds himself to be the &ldquo;Nebraska guy&rdquo; from <a href="https://xkcd.com/2347/" target="_blank" rel="noopener noreferrer">XKCD comic</a>.</p>
<p>When you look at the situation we&rsquo;re in this is the classic &ldquo;Nebraska guy problem&rdquo;: critical infrastructure maintained by a single person. pgBackRest is widely used in production, yet its sustainability dependson one individual being able to justify working on it. That does not seem fair and David did right to point this out with his move.</p>
<p>Of course, if anyone in the community chose to, they can still maintain the project by forking it. But why, since the problem is elsewhere?</p>
<p>Most people understand that engineers need to be paid for their work. What not everyone realizes is that the free for all software that the open source license provides does not mean free as in beer. Someone still needs to fund it!</p>
<p>Unfortunately &ldquo;someone&rdquo; almost certainly is going to be &ldquo;no-one&rdquo; unless &ldquo;anyone&rdquo; realizes they are going to miss the software if nobody maintains it anymore.</p>
<p>While there&rsquo;s a claim to be made that:</p>
<blockquote>
<p>Companies are as good as they have to and as bad as they are allowed to</p>
</blockquote>
<p>And often we see that an entity uses software they do not have to pay license fees for, treating this as cost optimization. There is also a large chunk of organizations that realize this is not a good long term strategy. Actively lowering the operational risk is important.</p>
<p>This is where foundations typically kick in: providing an easy way for organizations to contribute and ensure the longevity and healthiness of the projects. But PostgreSQL does not (yet) have one.</p>
<h2 id="where-are-we-now">Where are we now?<a class="anchor-link" id="where-are-we-now"></a></h2>
<p>There&rsquo;s a lot of backchannel talks happening.</p>
<p>Join the ones on:</p>
<ul>
<li>Telegram</li>
<li>Discord</li>
<li>Slack</li>
<li><a href="https://www.reddit.com/r/PostgreSQL/comments/1sx2ttg/pgbackrest_is_no_longer_being_maintained/" target="_blank" rel="noopener noreferrer">Reddit</a> (I don&rsquo;t have enough karma to engage there :/)</li>
</ul>
<p>or let us know what is your stance <a href="https://forums.percona.com/t/pgbackrest-is-eol/40720" target="_blank" rel="noopener noreferrer">(Percona Community Forum thread available)</a> so that we can represent you in the discussions we are having.</p>
<p>A lot of blog posts have been written on this subject, check out <a href="https://planet.postgresql.org/" target="_blank" rel="noopener noreferrer">Planet PostgreSQL</a> to find some of them! I particularly enjoyed some of them, the <a href="https://proopensource.it/blog/postgresql-ecosystem-problems-2026" target="_blank" rel="noopener noreferrer">one</a> from <a href="https://proopensource.it/stefanie-janine-stoelting.html" target="_blank" rel="noopener noreferrer">Stefanie Janine St&ouml;lting</a>, I feel I am mostly aligned with. PostgreSQL needs an Ecosystem Umbrella Foundation</p>
<h2 id="the-future-of-open-source-is-on-us">The future of open source is on us<a class="anchor-link" id="the-future-of-open-source-is-on-us"></a></h2>
<p>Reading that a project is EOL is triggering to me. When long-time maintainer announced plans to step away after more than a decade of work, instead of focusing on what the problem is that caused him to do so and how to solve the issue.Naming it &ldquo;dead&rdquo; complicate things even further. Labeling the project as &ldquo;dead&rdquo; doesn&rsquo;t solve the problem. Rather, it accelerates the wrong response. Users start looking for replacements instead of asking how to sustain the project.</p>
<p>This is not the way, young Padawan!</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/04/opensourcedoesntdie-youngpadawan.png" alt="blog/2026/04/opensourcedoesntdie-youngpadawan.png"></figure>
</p>
<p>We need a body that helps both users and authors by:</p>
<ol>
<li>Providing governance and a helping hand to the ecosystem. Yes, this is also funding</li>
<li>Providing guarantees of healthiness. This means users will have it easier to know the tools are in good shape.</li>
</ol>
<h2 id="so-whats-with-pgbackrest">So what&rsquo;s with pgBackRest<a class="anchor-link" id="so-whats-with-pgbackrest"></a></h2>
<p>While we talk here in the public, a lot of decisions are being made and Percona among other companies is working towards resolving this situation.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/04/opensourcedoesntdie-allyouneed.png" alt="blog/2026/04/opensourcedoesntdie-allyouneed.png"></figure>
</p>
<p>Have patience. Work is already underway behind the scenes, and the situation is evolving. There will be positive news resolving the situation coming soon, as Open Source doesn&rsquo;t die!</p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/04/30/open-source-doesnt-die.-it-gets-unfunded./">Open source doesn’t die. It gets unfunded.</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Open source doesn’t die. It gets unfunded.</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/04/30/open-source-doesnt-die-it-gets-unfunded/" />
      <id>https://percona.community/blog/2026/04/30/open-source-doesnt-die-it-gets-unfunded/</id>
      <updated>2026-04-30T11:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>If you are using PostgreSQL in any capacity very likely this week has started for you with a bang. pgBackRest, one of the most known tools for PostgreSQL, praised for the scalable and reliable way to do backups has announced that the project is currently archived.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/04/30/open-source-doesnt-die-it-gets-unfunded/">Open source doesn’t die. It gets unfunded.</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>If you are using PostgreSQL in any capacity very likely this week has started for you with a bang. pgBackRest, one of the most known tools for PostgreSQL, praised for the scalable and reliable way to do backups has announced that the project is currently archived.</p>
<h2 id="archived-you-mean-eol">Archived, <a href="https://www.reddit.com/r/PostgreSQL/comments/1sx2ttg/comment/oilzdag/?utm_source=share&amp;utm_medium=web3x&amp;utm_name=web3xcss&amp;utm_term=1&amp;utm_content=share_button" target="_blank" rel="noopener noreferrer">you mean EOL</a>?<a class="anchor-link" id="archived-you-mean-eol"></a></h2>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/04/opensourcedoesntdie-reddit.png" alt="blog/2026/04/opensourcedoesntdie-reddit.png"></figure>
</p>
<p>No! Open source software rarely has a hard &ldquo;end of life.&rdquo; What it does have are maintainership gaps and those can be just as serious.</p>
<p>It&rsquo;s different when PostgreSQL community announces a major version EOL. This happens because Community chooses to not to support it and move on to focus on newer versions.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/04/opensourcedoesntdie-thisisopensource.png" alt="blog/2026/04/opensourcedoesntdie-thisisopensource.png"></figure>
</p>
<p>Reading the message from David Steele, the long-time primary maintainer of pgBackRest you will not find &ldquo;end of life&rdquo; term. The project is marked read-only and no longer actively maintained, but that is not the same as being permanently dead.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/04/opensourcedoesntdie-maintenance.png" alt="blog/2026/04/opensourcedoesntdie-maintenance.png"></figure>
</p>
<p>pgBackRest is not &ldquo;end of life.&rdquo; There is no governing body declaring support ended. What happened is simpler and more common in open source: the maintainer can no longer afford to continue.</p>
<h2 id="so-what-happened-then">So what happened then?<a class="anchor-link" id="so-what-happened-then"></a></h2>
<p>This requires some story telling and I don&rsquo;t think that I can do it better than <a href="https://mydbanotebook.org/about/" target="_blank" rel="noopener noreferrer">L&aelig;titia Avrot</a> already did in her <a href="https://mydbanotebook.org/posts/pgbackrest-is-dead.-now-what/#what-happened" target="_blank" rel="noopener noreferrer">blogpost</a> (though I do not like the title):</p>
<blockquote>
<p>Crunchy Data, which had sponsored&nbsp;<code>pgBackRest</code>&nbsp;for most of its life and employed David, was sold. After that, David spent months looking for a position that would let him keep working on the project. He also tried to secure independent sponsorship. Neither worked out. He needs to make a living. The project requires sustained effort which he can no longer provide without being paid for it.</p>
</blockquote>
<p>This is the issue. An experienced developer, who wants to work on the project (that a big chunk of enterprises use) finds himself to be the &ldquo;Nebraska guy&rdquo; from <a href="https://xkcd.com/2347/" target="_blank" rel="noopener noreferrer">XKCD comic</a>.</p>
<p>When you look at the situation we&rsquo;re in this is the classic &ldquo;Nebraska guy problem&rdquo;: critical infrastructure maintained by a single person. pgBackRest is widely used in production, yet its sustainability dependson one individual being able to justify working on it. That does not seem fair and David did right to point this out with his move.</p>
<p>Of course, if anyone in the community chose to, they can still maintain the project by forking it. But why, since the problem is elsewhere?</p>
<p>Most people understand that engineers need to be paid for their work. What not everyone realizes is that the free for all software that the open source license provides does not mean free as in beer. Someone still needs to fund it!</p>
<p>Unfortunately &ldquo;someone&rdquo; almost certainly is going to be &ldquo;no-one&rdquo; unless &ldquo;anyone&rdquo; realizes they are going to miss the software if nobody maintains it anymore.</p>
<p>While there&rsquo;s a claim to be made that:</p>
<blockquote>
<p>Companies are as good as they have to and as bad as they are allowed to</p>
</blockquote>
<p>And often we see that an entity uses software they do not have to pay license fees for, treating this as cost optimization. There is also a large chunk of organizations that realize this is not a good long term strategy. Actively lowering the operational risk is important.</p>
<p>This is where foundations typically kick in: providing an easy way for organizations to contribute and ensure the longevity and healthiness of the projects. But PostgreSQL does not (yet) have one.</p>
<h2 id="where-are-we-now">Where are we now?<a class="anchor-link" id="where-are-we-now"></a></h2>
<p>There&rsquo;s a lot of backchannel talks happening.</p>
<p>Join the ones on:</p>
<ul>
<li>Telegram</li>
<li>Discord</li>
<li>Slack</li>
<li><a href="https://www.reddit.com/r/PostgreSQL/comments/1sx2ttg/pgbackrest_is_no_longer_being_maintained/" target="_blank" rel="noopener noreferrer">Reddit</a> (I don&rsquo;t have enough karma to engage there :/)</li>
</ul>
<p>or let us know what is your stance <a href="https://forums.percona.com/t/pgbackrest-is-eol/40720" target="_blank" rel="noopener noreferrer">(Percona Community Forum thread available)</a> so that we can represent you in the discussions we are having.</p>
<p>A lot of blog posts have been written on this subject, check out <a href="https://planet.postgresql.org/" target="_blank" rel="noopener noreferrer">Planet PostgreSQL</a> to find some of them! I particularly enjoyed some of them, the <a href="https://proopensource.it/blog/postgresql-ecosystem-problems-2026" target="_blank" rel="noopener noreferrer">one</a> from <a href="https://proopensource.it/stefanie-janine-stoelting.html" target="_blank" rel="noopener noreferrer">Stefanie Janine St&ouml;lting</a>, I feel I am mostly aligned with. PostgreSQL needs an Ecosystem Umbrella Foundation</p>
<h2 id="the-future-of-open-source-is-on-us">The future of open source is on us<a class="anchor-link" id="the-future-of-open-source-is-on-us"></a></h2>
<p>Reading that a project is EOL is triggering to me. When long-time maintainer announced plans to step away after more than a decade of work, instead of focusing on what the problem is that caused him to do so and how to solve the issue.Naming it &ldquo;dead&rdquo; complicate things even further. Labeling the project as &ldquo;dead&rdquo; doesn&rsquo;t solve the problem. Rather, it accelerates the wrong response. Users start looking for replacements instead of asking how to sustain the project.</p>
<p>This is not the way, young Padawan!</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/04/opensourcedoesntdie-youngpadawan.png" alt="blog/2026/04/opensourcedoesntdie-youngpadawan.png"></figure>
</p>
<p>We need a body that helps both users and authors by:</p>
<ol>
<li>Providing governance and a helping hand to the ecosystem. Yes, this is also funding</li>
<li>Providing guarantees of healthiness. This means users will have it easier to know the tools are in good shape.</li>
</ol>
<h2 id="so-whats-with-pgbackrest">So what&rsquo;s with pgBackRest<a class="anchor-link" id="so-whats-with-pgbackrest"></a></h2>
<p>While we talk here in the public, a lot of decisions are being made and Percona among other companies is working towards resolving this situation.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/04/opensourcedoesntdie-allyouneed.png" alt="blog/2026/04/opensourcedoesntdie-allyouneed.png"></figure>
</p>
<p>Have patience. Work is already underway behind the scenes, and the situation is evolving. There will be positive news resolving the situation coming soon, as Open Source doesn&rsquo;t die!</p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/04/30/open-source-doesnt-die-it-gets-unfunded/">Open source doesn’t die. It gets unfunded.</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Continued Commitment to Percona XtraDB Cluster</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/continued-commitment-to-percona-xtradb-cluster/" />
      <id>https://www.percona.com/blog/continued-commitment-to-percona-xtradb-cluster/</id>
      <updated>2026-04-30T09:53:09+00:00</updated>
      <author><name>Dennis Kittrell</name></author>
      <summary type="html"><![CDATA[<p>At Percona, our priority has always been to provide the open source database solutions that our users can count on for the long term. Percona XtraDB Cluster (PXC) is a core part of that promise, delivering the high availability, scalability, and data integrity that mission-critical MySQL deployments depend on. MariaDB has announced that September 30, … Continued<br />
The post Continued Commitment to Percona XtraDB Cluster appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/continued-commitment-to-percona-xtradb-cluster/">Continued Commitment to Percona XtraDB Cluster</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>At Percona, our priority has always been to provide the open source database solutions that our users can count on for the long term. Percona XtraDB Cluster (PXC) is a core part of that promise, delivering the high availability, scalability, and data integrity that mission-critical MySQL deployments depend on.</p>
<p>MariaDB has announced that September 30, 2026 will be the end-of-life date for continued maintenance and regular binary releases of MySQL Galera Cluster. We want to be clear about what this means for the organizations that rely on PXC: nothing is changing. Our commitment to PXC and the community that runs it is as strong as ever.</p>
<p>What is ending upstream is precisely what we already have in place. For anyone looking for an alternative path forward, PXC is the natural place to land.</p>
<h4>What PXC users can count on</h4>
<ul>
<li><strong>Our open Galera fork</strong>: Percona maintains its <a href="https://github.com/percona/galera/tree/4.x-8.0" target="_blank" rel="noopener">own Galera repository</a>, open today and staying that way. We track upstream Galera releases, carry the fixes our customers need, and keep the codebase fully available for the community. PXC is built on this work, on terms we control.</li>
<li><strong>Regular releases at the current cadence</strong>: Binary releases, bug fixes, and security patches continue to ship on the same terms and schedule our users have come to expect. You can review our full <a href="https://docs.percona.com/percona-xtradb-cluster/" target="_blank" rel="noopener">release history and release notes</a> on the Percona documentation site.</li>
<li><strong>Long-term support</strong>: PXC remains fully supported under our existing long-term support terms. If your organization is planning three to five years ahead, PXC is a safe foundation for those plans.</li>
<li><strong>Compatibility and ecosystem integration</strong>: Strong binary compatibility with MySQL and Percona Server for MySQL, tight integration with Percona XtraBackup and Percona Monitoring and Management, and continued support across Kubernetes and traditional deployment environments.</li>
</ul>
<h4>What we&rsquo;re continuing to invest in</h4>
<p>Our engineering teams remain committed to making PXC better, focused on the things that make it a trusted choice: performance, stability, security, and a smooth operator experience. That work continues at pace. The PXC you depend on today will keep getting better, and the PXC you are evaluating for tomorrow will be ready when you need it.</p>
<h2>Talk to us<a class="anchor-link" id="talk-to-us"></a></h2>
<p>If you have specific questions about your PXC deployment, your upgrade path, or your long-term high availability strategy, we&rsquo;d love to hear from you. Reach out to your Percona contact, post a question in the <a href="https://forums.percona.com" target="_blank" rel="noopener">Percona community forums</a>, or connect with our team directly. High availability is too important to leave to uncertainty, and we are here to make sure you have the clarity and the support you need.</p>
<p><!-- notionvc: f139f266-790e-4b33-b8a5-96be27606c4a --></p>
<p>The post <a href="https://www.percona.com/blog/continued-commitment-to-percona-xtradb-cluster/">Continued Commitment to Percona XtraDB Cluster</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/continued-commitment-to-percona-xtradb-cluster/">Continued Commitment to Percona XtraDB Cluster</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>From Ecosystem to Architecture: Expanding How We Look at MariaDB</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/from-ecosystem-to-architecture-expanding-how-we-look-at-mariadb/" />
      <id>https://mariadb.org/from-ecosystem-to-architecture-expanding-how-we-look-at-mariadb/</id>
      <updated>2026-04-30T07:46:58+00:00</updated>
      <author><name>Anna Widenius</name></author>
      <summary type="html"><![CDATA[<p>Over the past month, one question has been coming up with increasing frequency:<br />
What is the MySQL / MariaDB ecosystem?<br />
In most discussions, the answer tends to focus on contributors to the source code: engineers, committers, and core developers shaping the database itself. …<br />
Continue reading \"From Ecosystem to Architecture: Expanding How We Look at MariaDB\"<br />
The post From Ecosystem to Architecture: Expanding How We Look at MariaDB appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/from-ecosystem-to-architecture-expanding-how-we-look-at-mariadb/">From Ecosystem to Architecture: Expanding How We Look at MariaDB</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Over the past month, one question has been coming up with increasing frequency:<br>
What is the MySQL / MariaDB ecosystem?<br>
In most discussions, the answer tends to focus on contributors to the source code: engineers, committers, and core developers shaping the database itself. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/from-ecosystem-to-architecture-expanding-how-we-look-at-mariadb/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;From Ecosystem to Architecture: Expanding How We Look at MariaDB&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/from-ecosystem-to-architecture-expanding-how-we-look-at-mariadb/">From Ecosystem to Architecture: Expanding How We Look at MariaDB</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/from-ecosystem-to-architecture-expanding-how-we-look-at-mariadb/">From Ecosystem to Architecture: Expanding How We Look at MariaDB</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Adding a New Data Type to MariaDB with Type_handler – Part 1</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-1/" />
      <id>https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-1/</id>
      <updated>2026-04-30T06:12:31+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>This is the first part of the series about how to add a new data type to MariaDB using the Type_handler framework. A preliminary article has already been published to start the series; …<br />
Continue reading \"Adding a New Data Type to MariaDB with Type_handler – Part 1\"<br />
The post Adding a New Data Type to MariaDB with Type_handler – Part 1 appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-1/">Adding a New Data Type to MariaDB with Type_handler – Part 1</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>This is the first part of the series about how to add a new data type to MariaDB using the Type_handler framework. A preliminary article has already been published to start the series; &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-1/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Adding a New Data Type to MariaDB with Type_handler &ndash; Part 1&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-1/">Adding a New Data Type to MariaDB with Type_handler &ndash; Part 1</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-1/">Adding a New Data Type to MariaDB with Type_handler – Part 1</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Troubleshooting logical replication delay made easy</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/troubleshooting-logical-replication-delay-made-easy/" />
      <id>https://www.percona.com/blog/troubleshooting-logical-replication-delay-made-easy/</id>
      <updated>2026-04-30T04:57:02+00:00</updated>
      <author><name>Jobin Augustine</name></author>
      <summary type="html"><![CDATA[<p>This blog is based on a real production case in which users experienced a serious delay in logical replication. Let me try to explain how to approach similar cases and analyze them in an easy method, because lag in logical replication is a common problem, and we should expect it to come up for different … Continued<br />
The post Troubleshooting logical replication delay made easy appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/troubleshooting-logical-replication-delay-made-easy/">Troubleshooting logical replication delay made easy</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>This blog is based on a real production case in which users experienced a serious delay in logical replication. Let me try to explain how to approach similar cases and analyze them in an easy method, because lag in logical replication is a common problem, and we should expect it to come up for different environments. But sometimes troubleshooting can be challenging, especially on DBaaS environments where we won&rsquo;t get in-depth information at OS / hardware level. Such situations force us to deal with limited information which is available <b>within the PostgreSQL connection</b> (No host-level troubleshooting possible)</p>
<h2>The Case<a class="anchor-link" id="the-case"></a></h2>
<p>The case that triggered this blog was an attempt to migrate from one cloud vendor, to a recent version of PostgreSQL on a DBaaS offering of another cloud vendor. They started observing huge replication lag and reported to Percona. As usual, we started with pg_gather data collection.</p>
<p>(At Percona, we use pg_gather for diagnosis. Even though this blog and diagnosis refers to pg_gather report, any good diagnosis tool / scripts which can help to study the wait-event pattern and lag details could be able to help)</p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-45287" src="https://www.percona.com/wp-content/uploads/2026/04/CaseLogicalLag.png" alt="" width="1276" height="227"></p>
<p>We saw upto 4.5 terabyte lag is happening at the transmission side (Publisher) on the customer case. The <b>&ldquo;Transmission&rdquo;&nbsp; lag&rdquo; is the difference between the latest generated LSN and the LSN which the WAL Sender is able to send (sent_lsn of pg_stat_replication)</b>. That&rsquo;s a first indication that the problem is mainly at the publisher side (WAL Sender) and it is not able to send the information fast enough.</p>
<p>Next step of investigation is to understand what both those WAL Senders might be doing. The wait event information for each WAL Sender could provide a clear clue on where the delay is happening.</p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-45288" src="https://www.percona.com/wp-content/uploads/2026/04/CaseLogicalLagWait.png" alt="" width="572" height="131"></p>
<p>Both the WAL Senders are mainly waiting in &ldquo;<b>WalSenderWriteData</b>&rdquo; event upto 85% of its time. This is a very unusual level of wait.</p>
<p>Following is the logic behind this.</p>
<ol>
<li>Logical decoding hands a finished record to<br>
			<span id="urvanov-syntax-highlighter-69f2e2a30d6d9416087273" class="urvanov-syntax-highlighter-syntax urvanov-syntax-highlighter-syntax-inline  crayon-theme-classic crayon-theme-classic-inline urvanov-syntax-highlighter-font-monaco" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-pre urvanov-syntax-highlighter-code" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-e">WalSndWriteData</span><span class="crayon-sy">(</span><span class="crayon-sy">)</span></span></span></li>
<li>The data is queued in the<br>
			<span id="urvanov-syntax-highlighter-69f2e2a30d6e1854553405" class="urvanov-syntax-highlighter-syntax urvanov-syntax-highlighter-syntax-inline  crayon-theme-classic crayon-theme-classic-inline urvanov-syntax-highlighter-font-monaco" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-pre urvanov-syntax-highlighter-code" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-v">libpq</span></span></span>&nbsp; send buffer with<br>
			<span id="urvanov-syntax-highlighter-69f2e2a30d6e3649420448" class="urvanov-syntax-highlighter-syntax urvanov-syntax-highlighter-syntax-inline  crayon-theme-classic crayon-theme-classic-inline urvanov-syntax-highlighter-font-monaco" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-pre urvanov-syntax-highlighter-code" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-v">pq_putmessage_noblock</span></span></span></li>
<li>A non-blocking flush is attempted with<br>
			<span id="urvanov-syntax-highlighter-69f2e2a30d6e5548818300" class="urvanov-syntax-highlighter-syntax urvanov-syntax-highlighter-syntax-inline  crayon-theme-classic crayon-theme-classic-inline urvanov-syntax-highlighter-font-monaco" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-pre urvanov-syntax-highlighter-code" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-e">pq_flush_if_writable</span><span class="crayon-sy">(</span><span class="crayon-sy">)</span></span></span>&nbsp;.&nbsp; But when the kernel send buffer is full, internal_flush_buffer() returns 0 with <b>EAGAIN / EWOULDBLOCK</b>&nbsp; and data stays buffered &mdash;<br>
			<span id="urvanov-syntax-highlighter-69f2e2a30d6e7107501997" class="urvanov-syntax-highlighter-syntax urvanov-syntax-highlighter-syntax-inline  crayon-theme-classic crayon-theme-classic-inline urvanov-syntax-highlighter-font-monaco" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-pre urvanov-syntax-highlighter-code" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-e">pq_is_send_pending</span><span class="crayon-sy">(</span><span class="crayon-sy">)</span></span></span>&nbsp; becomes<br>
			<span id="urvanov-syntax-highlighter-69f2e2a30d6e9747931880" class="urvanov-syntax-highlighter-syntax urvanov-syntax-highlighter-syntax-inline  crayon-theme-classic crayon-theme-classic-inline urvanov-syntax-highlighter-font-monaco" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-pre urvanov-syntax-highlighter-code" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-t">true</span></span></span></li>
<li>The fast-path return is skipped, so it enters<br>
			<span id="urvanov-syntax-highlighter-69f2e2a30d6eb288901571" class="urvanov-syntax-highlighter-syntax urvanov-syntax-highlighter-syntax-inline  crayon-theme-classic crayon-theme-classic-inline urvanov-syntax-highlighter-font-monaco" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-pre urvanov-syntax-highlighter-code" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-e">ProcessPendingWrites</span><span class="crayon-sy">(</span><span class="crayon-sy">)</span></span></span></li>
<li>There,<br>
			<span id="urvanov-syntax-highlighter-69f2e2a30d6ec235984838" class="urvanov-syntax-highlighter-syntax urvanov-syntax-highlighter-syntax-inline  crayon-theme-classic crayon-theme-classic-inline urvanov-syntax-highlighter-font-monaco" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-pre urvanov-syntax-highlighter-code" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-e">WalSndWait</span><span class="crayon-sy">(</span><span class="crayon-v">WL_SOCKET_WRITEABLE</span><span class="crayon-h"> </span><span class="crayon-o">|</span><span class="crayon-h"> </span><span class="crayon-v">WL_SOCKET_READABLE</span><span class="crayon-sy">,</span><span class="crayon-h"> </span><span class="crayon-v">sleeptime</span><span class="crayon-sy">,</span><span class="crayon-h"> </span><span class="crayon-v">WAIT_EVENT_WAL_SENDER_WRITE_DATA</span><span class="crayon-sy">)</span></span></span>&nbsp; blocks until the socket is writable again or the subscriber sends a reply &mdash; that wait is what shows up as wait-event <b>WalSenderWriteData</b></li>
</ol>
<p>Source code reference : <a href="https://github.com/postgres/postgres/blob/master/src/backend/replication/walsender.c#L1673">src/backend/replication/walsender.c</a></p>
<p>Which means that this wait event could happen due to the following reasons which I could think about. I would appreciate your comments if you know more reasons.</p>
<ol>
<li>The subscriber&rsquo;s apply worker not consuming the stream fast enough (apply lock contention, slow I/O, heavy CPU load on the subscriber) &mdash; its TCP receive buffer fills up, the TCP window shrinks to zero,&nbsp; the publisher&rsquo;s<br>
			<span id="urvanov-syntax-highlighter-69f2e2a30d6ee441880754" class="urvanov-syntax-highlighter-syntax urvanov-syntax-highlighter-syntax-inline  crayon-theme-classic crayon-theme-classic-inline urvanov-syntax-highlighter-font-monaco" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-pre urvanov-syntax-highlighter-code" style="font-size: 12px !important;line-height: 15px !important;font-size: 12px !important"><span class="crayon-e">send</span><span class="crayon-sy">(</span><span class="crayon-sy">)</span></span></span>&nbsp; returns EAGAIN, and the WAL sender spins in the loop.</li>
<li>Saturated network bandwidth between publisher and subscriber. There we have two cases. Traffic originating from Publisher to Subscriber could be slow OR handshake/acknowledgement traffic from Subscriber back to Publisher could be slow. The symptoms may differ.</li>
<li>Large decoded transactions produce bursts of WalSndWriteData calls faster than the network can absorb them. This is generally a temporary problem and the cluster might catchup once the overhead of the large transaction is over.</li>
</ol>
<p>Now the question would be : Now we know all the probable causes, but how to narrow down to the most probable cause ?, so that we can have an action plan.</p>
<p>At Percona, our engineers take time to put all hypotheses for testing, trying to simulate similar conditions and produce observable and reproducible cases. One might argue that we can use low level tracing / OS level tools at this stage to narrow down. Definitely, Yes. That&rsquo;s the most appropriate thing to do. However many of the users may not have low level access and DBaaS offerings prevent it by design. But the good news is that wait &ndash; event patterns can tell us a story in more detail.</p>
<h3><b>Slow Network traffic from Publisher to subscriber</b><a class="anchor-link" id="slow-network-traffic-from-publisher-to-subscriber"></a></h3>
<p>If the network connectivity from Publisher side is slow or suffering with high latency, the send buffer won&rsquo;t get cleared fast enough. Resulting in repeated attempts to send the data.</p>
<p>When we simulated the situation in the lab, we observed the similar Transmission side lag</p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-45289" src="https://www.percona.com/wp-content/uploads/2026/04/LagInTheLab.png" alt="" width="1549" height="113"></p>
<p>Since this is a network connection problem, automatically the acknowledgment coming from the subscriber side will also be delayed and expected to show lags in all Write, Flush and Replay stages, which is visible in the data collection.</p>
<p>Obviously, our next question is what WALSender is doing ?. The wait events reveal that</p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-45290" src="https://www.percona.com/wp-content/uploads/2026/04/LagIntheLabWait.png" alt="" width="1658" height="69"></p>
<p>The WAL sender is struggling to send data to Standby. This matches with what the user was seeing in their database.</p>
<p>Meanwhile, at the subscriber side, what we could see is that the apply worker is majorly sitting idle in the main loop: <b>LogicalApplyMain</b></p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-45291" src="https://www.percona.com/wp-content/uploads/2026/04/WaitingInLogicalApplyMain.png" alt="" width="1615" height="52"></p>
<p>Two other major symptoms to be noted in the cases is 1). much smaller &ldquo;Write lag&rdquo; and compared to &ldquo;Transmission lag&rdquo; and&nbsp; 2.). Both the subscribers are suffering the lag, which is less probable if the problem is on the subscriber side.&nbsp; Even additional clues like data collection running longer when executed from the publisher side about the subscriber instance is also supplementary evidence.</p>
<p>All the above symptoms helps us to conclude with a reasonable level of confidence that the network traffic from the Publisher is the problem.</p>
<h3><b>Overloaded or slow Subscriber node</b><a class="anchor-link" id="overloaded-or-slow-subscriber-node"></a></h3>
<p>The problem could be caused by subscriber not communicating fast enough with Publisher. In such cases the replication lag is expected. I tested that scenario and the following is the observation.<img decoding="async" loading="lazy" class="alignnone size-full wp-image-45292" src="https://www.percona.com/wp-content/uploads/2026/04/SlowSubscriberLag.png" alt="" width="1316" height="118"></p>
<p>The cause of the lag shifts from the &ldquo;Transmission lag&rdquo; to &ldquo;Replica Write Lag&rdquo;, which&nbsp; is the difference between <b>send_lsn</b> and <b>write_lsn</b>.</p>
<p>The WAL Sender / publisher side don&rsquo;t have any problem in sending<img decoding="async" loading="lazy" class="alignnone size-full wp-image-45293" src="https://www.percona.com/wp-content/uploads/2026/04/WALSenderSleeping.png" alt="" width="1997" height="71"></p>
<p>That looks really cool. The major wait event is <strong>WalSenderWaitForWal</strong>. Which means that the WAL sender is just waiting for the next WAL to be flushed and ready, In other words, sleeping until the next commit.</p>
<p>However, the situation on the Subscriber side is different. Unlike in the previous case, the apply worker could be busy with all sorts of work, no more free time to wait in the main loop.</p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-45294" src="https://www.percona.com/wp-content/uploads/2026/04/SubscriberBusyWaits.png" alt="" width="1927" height="52"></p>
<p>The wait events and their percentage may vary depending on the performance-bottleneck on the subscriber side.</p>
<h3><b>Slow traffic from Subscriber to Publisher</b><a class="anchor-link" id="slow-traffic-from-subscriber-to-publisher"></a></h3>
<p>The impact of the network traffic from the Subscriber side back to Publisher has less effect than that from Publisher side. Because the data flow is from Publisher to Subscriber. The Subscriber needs to send only handshake acknowledgment information back to the publisher, which requires less bandwidth. So an apply worker can be waiting in the main loop (<b>LogicalApplyMain</b>)</p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-45295" src="https://www.percona.com/wp-content/uploads/2026/04/SlowTrafficFromSubtoPub.png" alt="" width="1999" height="76"></p>
<p>But contrary to expectations, there could be cases of significant CPU usage if there are repeated attempts to reach primary, it may be consuming significant CPU cycles.&nbsp; If we are suspecting network problems from the subscriber side, paying close attention to PostgreSQL logs is important.</p>
<p>There can be timeout captured in PostgreSQL logs at the publisher side</p>
<pre class="urvanov-syntax-highlighter-plain-tag">2026-04-23 17:34:07.078 UTC [36057] postgres@postgres LOG:  terminating walsender process due to replication timeout
2026-04-23 17:34:07.078 UTC [36057] postgres@postgres CONTEXT:  slot "sub", output plugin "pgoutput", in the commit callback, associated LSN DA/70450B8
2026-04-23 17:34:07.078 UTC [36057] postgres@postgres STATEMENT:  START_REPLICATION SLOT "sub" LOGICAL D9/DF13B50 (proto_version '4', streaming 'parallel', origin 'any', publication_names '"pub"')</pre>
<p>Corresponding errors might be appearing at subscriber side also</p>
<pre class="urvanov-syntax-highlighter-plain-tag">2026-04-23 17:36:00.933 UTC [38413] ERROR:  could not receive data from WAL stream: SSL connection has been closed unexpectedly
2026-04-23 17:36:00.940 UTC [38428] LOG:  logical replication apply worker for subscription "sub" has started
2026-04-23 17:36:00.946 UTC [18241] LOG:  background worker "logical replication apply worker" (PID 38413) exited with exit code 1</pre>
<p>All these are indications of poor connection.</p>
<h3>Summary<a class="anchor-link" id="summary"></a></h3>
<p>PostgreSQL Wait events provide lots of visibility into PostgreSQL and underlying infrastructure problems. Reading it with PostgreSQL statistics information (pg_stat_*) and PostgreSQL log&nbsp; could provide answers for a lot of questions as follows easily.</p>
<ol>
<li>Where is the problem ?. Which side of replication is lagging ?</li>
<li>What are WAL Senders and WAL Receivers doing ? Are they busy doing something ? Is there anything pending or sitting idle ?</li>
<li>Are there any hits of underlying infrastructure problems ?</li>
<li>Is the problem correlatable with the wait events ?</li>
</ol>
<p>Collecting and correlating the wait-event data from both sides of the replication, checking the LSN differences,&nbsp; and information from PostgreSQL logs&nbsp; gives us a complete picture.</p>
<p>All it takes is just a couple of minutes! With the right tools and methods in hand. I would like to encourage readers to make use of wait event pattern analysis for easy spotting of performance bottlenecks, if you are not doing it already. It can save you from the treachery of all indepth tracing. Observed Replication lag can be treated as a symptom of something more serious underlying.</p>
<p>This blog is written for those who don&rsquo;t have access to OS level, But if we have access, getting into details is far easier. For example, the send queue (Send-Q) of the Publisher host can be checked like:</p>
<pre class="urvanov-syntax-highlighter-plain-tag">postgres@node0:~$ ss -tnp
State            Recv-Q            Send-Q                         Local Address:Port                       Peer Address:Port             Process
ESTAB            0                 18791538                          172.18.0.2:5432                         172.18.0.3:44974             users:(("postgres",pid=48178,fd=11))</pre>
<p>&nbsp;</p>
<p>The post <a href="https://www.percona.com/blog/troubleshooting-logical-replication-delay-made-easy/">Troubleshooting logical replication delay made easy</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/troubleshooting-logical-replication-delay-made-easy/">Troubleshooting logical replication delay made easy</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>OIDC error scenarios</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/04/30/oidc-error-scenarios/" />
      <id>https://percona.community/blog/2026/04/30/oidc-error-scenarios/</id>
      <updated>2026-04-30T00:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>Last time, in OIDC in PostgreSQL: With Keycloak, we created a working demo setup that was able to successfully authenticate a user using OIDC.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/04/30/oidc-error-scenarios/">OIDC error scenarios</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Last time, in <a href="https://percona.community/blog/2026/01/19/oidc-in-postgresql-with-keycloak/">OIDC in PostgreSQL: With Keycloak</a>, we created a working demo setup that was able to successfully authenticate a user using OIDC.</p>
<p>In this blog post, we follow the same example, but instead of the success story, we explore how OAuth keeps our PostgreSQL servers secure.</p>
<p>We won&rsquo;t focus on complex attack vectors, like the examples in the <a href="https://percona.community/blog/2025/11/17/oidc-in-postgresql-how-it-works-and-staying-secure/">second blog post</a> in the OIDC series.<br>
Instead of social engineering, we&rsquo;ll look at practical errors, misconfigurations and honest mistakes &ndash; understanding error messages and how to fix them.</p>
<h3 id="improved-test-setup">Improved test setup<a class="anchor-link" id="improved-test-setup"></a></h3>
<p>Along with the step by step tutorial, previously we also linked a <a href="https://github.com/Percona-Lab/pg_oidc_validator/tree/main/examples/keycloak" target="_blank" rel="noopener noreferrer">docker/podman compose configuration</a>.<br>
This is still available, and we even improved it for testing the error scenarios.</p>
<p>If you want to update the configuration manually instead, this is what changed: we duplicated everything!</p>
<ul>
<li>Instead of a single testuser, we have two: <code>testuser</code> and <code>testuser2</code>, both using the same <code>asdfasdf</code> password</li>
<li>Instead of one client, we have two: <code>pgtest</code> and <code>pgtest2</code></li>
<li>Instead of one scope, we have two: <code>pgscope</code>, <code>pgscope2</code></li>
<li>Instead of one realm, we have two &ndash; containing exactly the same setup: <code>pgrealm</code> and <code>wrongrealm</code></li>
</ul>
<p>A role named <code>pgrole</code> is also defined.<br>
The <code>pgtest2</code> client and <code>pgscope2</code> scope both require the <code>pgrole</code> role.<br>
Only <code>testuser2</code> is assigned this role; <code>testuser</code> does not have it.</p>
<p>The following table summarizes the access matrix:</p>
<table>
<thead>
<tr>
<th></th>
<th>pgtest</th>
<th>pgtest2</th>
<th>pgscope</th>
<th>pgscope2</th>
</tr>
</thead>
<tbody>
<tr>
<td>testuser</td>
<td>OK</td>
<td>denied</td>
<td>OK</td>
<td>denied</td>
</tr>
<tr>
<td>testuser2</td>
<td>OK</td>
<td>OK</td>
<td>OK</td>
<td>OK</td>
</tr>
</tbody>
</table>
<h3 id="success-despite-a-fatal-error">Success despite a FATAL error?<a class="anchor-link" id="success-despite-a-fatal-error"></a></h3>
<p>However, before we start using all these additional items, let&rsquo;s go back to the end of the Keycloak story, where we succeeded in logging in.<br>
Or did we?<br>
While <code>psql</code> logged us in, if we checked the server error log, we could see the following there:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-0" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-0">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"</span></span></code></pre>
</div>
</div>
</div>
<p>But if we are observant enough, this message is logged before we even go to the device authentication website of Keycloak, and enter the authentication code.<br>
This isn&rsquo;t a real error, it&rsquo;s just a side effect of how OAuth is implemented internally, and will most likely be fixed in PostgreSQL 19, the FATAL message will no longer show up.</p>
<p>As for PostgreSQL 18, unfortunately, we have to live with this.<br>
This also means that we can&rsquo;t rely on simply looking for OAuth authentication errors in the server log, because all OAuth authentication failures will result in exactly the same message, no matter if they are logged because of this harmless situation or because of a real authentication issue.</p>
<p>A workaround is to rely on the validators instead: since the server is unaware of the exact error situation anyway &ndash; it delegates validation to the validator &ndash; these plugins will print out much more detailed <em>log messages</em>.<br>
We&rsquo;ll see some examples later with pg_oidc_validator, as we explore the error scenarios.</p>
<p>However, keep in mind that the sentence above says <em>log messages</em>, and not <em>errors</em> or <em>fatal errors</em>.<br>
Validators are not allowed to print out ERROR and FATAL messages for authentication failures, so users have to look for WARNING or LOG level messages for the details.<br>
In practice, PostgreSQL will still print the same generic FATAL error message about OAuth bearer authentication failing &ndash; but it will appear after the validator-specific WARNING or LOG messages that contain the actual diagnostic information.</p>
<h3 id="why-does-it-happen">Why does it happen?<a class="anchor-link" id="why-does-it-happen"></a></h3>
<p>Earlier we already established that PostgreSQL validates that the client and the server use the same issuer, but didn&rsquo;t go into more detail than this.</p>
<p>Usually when a service validates user input, it does so on the server.<br>
The main reason for this is that developers can trust the backend, controlled by administrators, while they can&rsquo;t trust the frontend, potentially used by malicious users.</p>
<p>But are we validating user input in this case?<br>
Why does the user even have to specify the issuer, since the server already knows it, it&rsquo;s in the HBA configuration?</p>
<p>Because this check isn&rsquo;t the server validating the user, it&rsquo;s the opposite:<br>
the user validating the server.</p>
<ol>
<li>The client sends an empty connection request to the server.</li>
<li>The server confirms that we are using OAuth, and sends back its issuer URL.</li>
<li>The client checks whether the issuer it is planning to use &ndash; or has already used, if it already has a valid token &ndash; matches the one sent by the server.
<ul>
<li>If it doesn&rsquo;t match, it aborts the login attempt and prints an error.</li>
<li>If it does match, it continues with a real authentication attempt.</li>
</ul>
</li>
</ol>
<pre class="mermaid">
sequenceDiagram
participant C as psql (Client)
participant S as PostgreSQL Server
participant K as Keycloak
C-&gt;&gt;S: Connection request (no token)
S--&gt;&gt;S: No token, auth fails
Note right of S: FATAL logged here (harmless side effect)
S-&gt;&gt;C: OAuth challenge + issuer URL
C--&gt;&gt;C: Compare issuer URL with oauth_issuer
alt Issuer mismatch
C--&gt;&gt;C: Abort with error
else Issuer matches
C-&gt;&gt;K: Device authorization flow
K-&gt;&gt;C: Access token
C-&gt;&gt;S: Connection request (with token)
S-&gt;&gt;S: Validator checks token
S-&gt;&gt;C: Authentication success
end
</pre>
<p>The FATAL error in the server log is a side effect of the first empty authentication attempt, that wasn&rsquo;t fixed in time before the PG18 release.</p>
<h3 id="why-do-we-need-this-check">Why do we need this check?<a class="anchor-link" id="why-do-we-need-this-check"></a></h3>
<p>With the improved configuration, we can test what happens when we specify the wrong issuer:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-2" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-2">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">bin/psql -h 127.0.0.1 'dbname=postgres oauth_issuer=https://keycloak:8443/realms/wrongrealm oauth_client_id=pgtest'
</span></span><span class="line"><span class="cl">psql: error: connection to server at "127.0.0.1", port 5432 failed: server's discovery document at https://keycloak:8443/realms/pgrealm/.well-known/openid-configuration (issuer "https://keycloak:8443/realms/pgrealm") is incompatible with oauth_issuer (https://keycloak:8443/realms/wrongrealm)</span></span></code></pre>
</div>
</div>
</div>
<p>Notice that compared to the correct command, which had the pgrealm, we are using the other Keycloak realm.<br>
And if we check the server log, we can see that there are no additional log messages there &ndash; we see a single OAuth FATAL error, which is not a real error, just the side effect we are investigating.<br>
This error is entirely on the client side.</p>
<p>And that brings us back to the question:<br>
why do we need this?</p>
<p>On one hand, it helps us prevent honest mistakes early.<br>
In our example, wrongrealm and pgrealm are exactly the same &ndash; they have users with the same name, scopes with the same names, clients with the same names.<br>
If there&rsquo;s a misconfiguration, and the server and the client use different realms in a similar setup, everything would seem to work &ndash; the user trying to log in would be able to log in, get a token, psql would send it to the server&hellip;<br>
and then on the server the validator would reject it &ndash; assuming that it is a good validator, like pg_oidc_validator.<br>
No harm done &ndash; other than disclosing a token to the server that shouldn&rsquo;t have been sent there &ndash;, but figuring out what the problem is could take a while.</p>
<p>On the other hand: what if we aren&rsquo;t dealing with a malicious user, but a malicious server?</p>
<p>In the previous attack vectors we showcased, the attacker was always a third party:<br>
somebody who wanted to steal access to the database server.</p>
<p>But we don&rsquo;t necessarily need a different unknown adversary, it could be the server we are using:<br>
do we absolutely know and trust its administrators?<br>
Sometimes yes, sometimes no.</p>
<p>Those administrators might be aware that we are also using OAuth for something else, and might plot to gain access to it.<br>
So instead of sending us the issuer we expect, the server sends us something else &ndash; for example a spoofed site, tricking us to complete login into a different service.</p>
<p>Remember the earlier situation where the Fake Photo Gallery Website used Client ID spoofing to gain access to the PostgreSQL Database?<br>
This situation is basically the same &ndash; the only difference is that this time PostgreSQL Database is trying to gain access to Photo Gallery.</p>
<pre class="mermaid">
sequenceDiagram
participant U as User (psql)
participant M as Malicious PostgreSQL Server
participant SSO as Other service
U-&gt;&gt;M: Connection request
M-&gt;&gt;U: OAuth challenge + spoofed issuer URL (instead of expected issuer)
Note over U: Without issuer check, user proceeds
U-&gt;&gt;SSO: Authenticates, thinking it is for PostgreSQL
SSO-&gt;&gt;U: Access token (valid for a different service)
U-&gt;&gt;M: Sends token to server
Note over M: Malicious server now holds a token valid for the other service
</pre>
<p>The client-side issuer check prevents this: psql compares the issuer URL from the server against the <code>oauth_issuer</code> it was configured with, and aborts if they don&rsquo;t match.</p>
<p>While requiring the client to specify the issuer may seem redundant, it&rsquo;s an important safeguard that prevents tokens from being disclosed to malicious servers.</p>
<h3 id="can-we-verify-the-validator">Can we verify the validator?<a class="anchor-link" id="can-we-verify-the-validator"></a></h3>
<p>If we specify an incorrect issuer, the client rejects it before completing the OAuth flow &ndash; that&rsquo;s great, but this means the validator isn&rsquo;t part of the picture.<br>
Can we even test that a validator handles this situation correctly, to verify that it properly rejects an attempt with an incorrect issuer?<br>
Security-aware users might want to double check that somebody using a modified psql is also properly rejected.</p>
<p>This is possible: in our next blog post, we&rsquo;ll see how to implement custom clients outside psql, possibly using other OAuth flows.<br>
In that scenario, we&rsquo;ll be able to send custom tokens to the server, which has many uses &ndash; one of which is internal testing of OAuth validators.</p>
<p>Rest assured, pg_oidc_validator handles this correctly &ndash; and we&rsquo;ll show you how to verify it yourself in the next post.<br>
If you are using a different validator, stay tuned to see how you can verify it!</p>
<p>With pg_oidc_validator, you will see something like this in the server log:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-4" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-4">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">WARNING: OAuth validation failed with exception: claim value does not match expected value
</span></span><span class="line"><span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"
</span></span><span class="line"><span class="cl">DETAIL: Connection matched file "/pg_hba.conf" line 119: "host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email",map=kcmap"</span></span></code></pre>
</div>
</div>
</div>
<p>Here &ldquo;claim value does not match expected value&rdquo; means that a field (claim) in the JWT doesn&rsquo;t match our expectation.<br>
While this might seem generic, currently pg_oidc_validator only validates exactly one field in this way: the issuer.</p>
<p>On the client side, you can see the following generic error message:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-5" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-5">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Connection error: connection to server at "127.0.0.1", port 5432 failed: retrying connection with new bearer token
</span></span><span class="line"><span class="cl">connection to server at "127.0.0.1", port 5432 failed: FATAL: OAuth bearer authentication failed for user "testuser"</span></span></code></pre>
</div>
</div>
</div>
<p>Which is generally true for most OAuth errors &ndash; validators are expected not to provide detailed information about why they reject a connection back to the client, to limit the information available to potential attackers.</p>
<h3 id="signature-failure">Signature failure<a class="anchor-link" id="signature-failure"></a></h3>
<p>For some it might be surprising that we are getting an error about the issuer, and not about the token.<br>
Why is that?</p>
<p>The reason we use JWTs for access tokens is because they are cryptographically signed tokens.<br>
While they contain the payload in clear text, the token ends with a signature, a proof that it was generated by the issuer we trust.</p>
<p>This means that if the token was generated by a different issuer, it is signed by a different key.</p>
<p>However, the order of operations inside the validator is different:<br>
first we validate the fields in the cleartext data we have strong expectations about &ndash; in this case the issuer.<br>
Then, after that&rsquo;s valid, we also verify that the signature matches the public key of the issuer.</p>
<p>Since in the above situation the issuer is different, we never get to the point of signature validation.</p>
<p>To do that, somebody has to tamper with the token.<br>
For example, an attacker realizes that we require a specific scope, and since JWTs contain everything in clear text, decides to edit the <code>scp</code> claim and insert <code>pgscope</code> into it.<br>
In that situation, the issuer matches, the validator verifies the signature, and we end up with a different error:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-6" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-6">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">WARNING: OAuth validation failed with exception: failed to verify signature: VerifyFinal failed
</span></span><span class="line"><span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"
</span></span><span class="line"><span class="cl">DETAIL: Connection matched file "/pg_hba.conf" line 119: "host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email",map=kcmap"</span></span></code></pre>
</div>
</div>
</div>
<p>The client side error message didn&rsquo;t change with this &ndash; this is clearly an attack attempt, we do not have to provide nice error messages for malicious users.</p>
<h3 id="what-about-expired-tokens">What about expired tokens?<a class="anchor-link" id="what-about-expired-tokens"></a></h3>
<p>Another interesting scenario you might wonder about is token lifetime:<br>
in OAuth, tokens have a limited period in which they are valid.</p>
<p>PostgreSQL currently has no facilities to enforce token lifetime when a connection is active &ndash; once somebody is logged in, they stay logged in until they disconnect for some reason &ndash;, but validators are expected to validate that tokens are still valid at least during authentication.</p>
<p>Similarly to the previous situation, testing this without a custom client isn&rsquo;t possible, as psql always asks for a new token during the connection attempt, there is no way to send an earlier token with it.</p>
<p>As in the previous example, this scenario is rejected by pg_oidc_validator, which logs the following message on the server:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-7" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-7">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">WARNING: OAuth validation failed with exception: token expired
</span></span><span class="line"><span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"
</span></span><span class="line"><span class="cl">DETAIL: Connection matched file "/pg_hba.conf" line 119: "host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email",map=kcmap"</span></span></code></pre>
</div>
</div>
</div>
<p>On the client side, you can only see the same generic error message as before.</p>
<p>While this doesn&rsquo;t seem too user friendly, keep in mind that both of these errors can only happen with faulty clients.<br>
Clients can, and should verify both the issuer and the expiration time before connecting to the server, and they should be able to provide nice error messages to the users based on that.</p>
<h3 id="scope-mismatch">Scope mismatch<a class="anchor-link" id="scope-mismatch"></a></h3>
<p>After the previous two situations, which are untestable with <code>psql</code>, let&rsquo;s move to the realm of errors which don&rsquo;t require custom code.</p>
<p>In the first and second blog posts we tried to emphasize how important scopes are in OAuth, how they can help prevent accidents.<br>
Obviously, validators have to make sure that all the scopes the server asked for are present in the received token.<br>
Having more scopes isn&rsquo;t an issue &ndash; sometimes clients use the same token for multiple services &ndash;, but missing a required scope should be an error.</p>
<p>To verify what happens in this situation, we can simply modify the pg_hba line to include a scope that doesn&rsquo;t exist on the server, for example adding <code>fooscope</code>:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-8" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-8">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email fooscope",map=kcmap</span></span></code></pre>
</div>
</div>
</div>
<p>And then we can connect with psql as before:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-9" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-9">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">bin/psql -h 127.0.0.1 'dbname=postgres oauth_issuer=https://keycloak:8443/realms/pgrealm oauth_client_id=pgtest'</span></span></code></pre>
</div>
</div>
</div>
<p>Which should result in the following detailed error message in the server log:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-10" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-10">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">LOG: Authorization failed because of scope mismatch. Required scopes: email, fooscope, pgscope. Received scopes: email, pgscope, profile
</span></span><span class="line"><span class="cl">LOG: OAuth bearer authentication failed for user "testuser"
</span></span><span class="line"><span class="cl">DETAIL: Validator failed to authorize the provided token.
</span></span><span class="line"><span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"
</span></span><span class="line"><span class="cl">DETAIL: Connection matched file "/pg_hba.conf" line 119: "host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email fooscope",map=kcmap"</span></span></code></pre>
</div>
</div>
</div>
<p>Similarly to the previous scenarios, this is completely validator specific, we can only showcase our validator.</p>
<p>This scenario also depends on the OAuth flow used and the identity provider.<br>
<strong>Note:</strong> Keycloak, for example, permits unknown scopes for the device flow &ndash; it simply ignores them and returns the scopes it can.<br>
However, it doesn&rsquo;t do that for other flows &ndash; the Token Endpoint rejects unknown scopes with an error and doesn&rsquo;t provide an access token.</p>
<p>On the client side, the error is the same as before &ndash; no details about what&rsquo;s missing.<br>
Which is fine in this situation, as this is clearly a configuration error, something the administrators have to figure out and fix.</p>
<p>Now let&rsquo;s see the error slightly differently.</p>
<p>The above example worked with the unmodified keycloak setup, described in the previous blog, but we have an improved test setup for this one.<br>
Instead of using a non existent foo scope, let&rsquo;s change our requirement to <code>pgscope2</code>, which requires <code>pgrole</code>:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-11" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-11">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope2 email",map=kcmap</span></span></code></pre>
</div>
</div>
</div>
<p>And similarly add <code>testuser2</code> to pg_ident, so both can log in:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-12" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-12">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># MAPNAME SYSTEM-USERNAME DATABASE-USERNAME
</span></span><span class="line"><span class="cl">kcmap testuser@example.com testuser
</span></span><span class="line"><span class="cl">kcmap testuser2@example.com testuser2</span></span></code></pre>
</div>
</div>
</div>
<p>In this new setup, we transformed the configuration problem into a permission issue where testuser2 can log in and testuser can not.</p>
<p>The error message on the client side is unchanged, it still doesn&rsquo;t say &ldquo;permission denied&rdquo; or &ldquo;scope mismatch&rdquo;, or anything like that.<br>
This is debatable, but it is still mainly a task for administrators, and not the user:<br>
somebody will have to investigate the permission setup on keycloak, and fix it, if testuser also needs access to the server.</p>
<h3 id="unknown-user">Unknown user<a class="anchor-link" id="unknown-user"></a></h3>
<p>Another common error source is a problem with the user mapping.<br>
In our example we are using a pg_ident file with an email, but it would be similar with other configurations.</p>
<p>Regardless of the setup, there are many reasons why we can&rsquo;t properly look up a username:</p>
<ul>
<li>using an incorrect field for <code>authn_field</code></li>
<li>missing an entry from <code>pg_ident</code></li>
<li>having a typo in the name either in <code>pg_ident</code> or in keycloak</li>
<li>and so on</li>
</ul>
<p>In all situations, the error message for this case won&rsquo;t be generated in the validator, but in the PostgreSQL user mapping code instead.<br>
For example, if you previously added <code>testuser2</code> to the ident file, comment it out and try to log in with it again:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-13" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-13">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">LOG: no match in usermap "kcmap" for user "testuser" authenticated as "testuser2@example.com"
</span></span><span class="line"><span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"
</span></span><span class="line"><span class="cl">DETAIL: Connection matched file "/pg_hba.conf" line 119: "host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email",map=kcmap"</span></span></code></pre>
</div>
</div>
</div>
<p>In an alternative configuration &ndash; which is not part of the sample keycloak configuration &ndash; it is possible to create a custom claim &ldquo;postgres_username&rdquo; on keycloak, and skip the map file completely.<br>
In this situation, a mismatched username would result in a slightly different error message:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-14" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-14">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">LOG: provided user name (testuser) and authenticated user name (testuser2) do not match
</span></span><span class="line"><span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"
</span></span><span class="line"><span class="cl">DETAIL: Connection matched file "/pg_hba.conf" line 119: "host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email""</span></span></code></pre>
</div>
</div>
</div>
<h3 id="connection-problems">Connection problems<a class="anchor-link" id="connection-problems"></a></h3>
<p>While it usually isn&rsquo;t a configuration or permission problem, it is possible that we have a network issue:<br>
either a localized routing error, where the client can connect to the identity provider but the server can&rsquo;t, or a situation where the identity provider / network crashed between obtaining the access token and verifying it on the server.</p>
<p>The client executable has an access token and sends it to the server, which then has to validate it without being able to communicate with the identity provider.<br>
This is another situation which is difficult to validate with <code>psql</code>, but it is relatively easy with a custom client.</p>
<p>Our OIDC validator has to connect to the identity provider for two reasons:</p>
<ul>
<li>One, to retrieve the discovery document which contains the URL of the JWKS endpoint &ndash; which stores the public keys of the issuer</li>
<li>Two, to retrieve the public keys using that JWKS endpoint</li>
</ul>
<p>The validator also follows HTTP Cache headers:<br>
for example, if the server allows caching the keys for 4 days, the validator only retrieves them for the first attempt, and then keeps using them for that time.<br>
After it passes, it connects to the server one more time, and if it again receives a 4 day window, it will keep using the keys for 4 more days.<br>
This means that with a proper provider setup, the validator might not even notice a short service loss.</p>
<p>Fortunately for our testing, but not so fortunately for production use, keycloak doesn&rsquo;t support JWKS caching at all.</p>
<p>An inaccessible OIDC server will result in logs similar to:</p>
<div class="code-block">
<div class="code-block__header"><button class="code-block__copy" type="button" data-copy-target="codeblock-15" aria-label="Copy code to clipboard"><br>
<span class="code-block__copy-default">Copy</span><br>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span><br>
</button>
</div>
<div class="code-block__content" id="codeblock-15">
<div class="highlight">
<pre class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">WARNING: OAuth validation failed with exception: HTTP request failed: Could not connect to server
</span></span><span class="line"><span class="cl">FATAL: OAuth bearer authentication failed for user "testuser"
</span></span><span class="line"><span class="cl">DETAIL: Connection matched file "/pg_hba.conf" line 119: "host all all 127.0.0.1/32 oauth issuer=https://keycloak:8443/realms/pgrealm,scope="pgscope email",map=kcmap"</span></span></code></pre>
</div>
</div>
</div>
<p>Where the exact error message depends on the situation &ndash; a timeout, internal server error, etc, all would result in slightly different error messages, while a timeout would also slow down the response time of the authentication attempt.</p>
<h3 id="lets-run-without-errors">Let&rsquo;s run without errors!<a class="anchor-link" id="lets-run-without-errors"></a></h3>
<p>We hope these examples will be useful for everybody. To avoid errors, to diagnose problems, and to simply understand the security model and guarantees given by OAuth and validators.</p>
<p>While this is not an all-inclusive list, as we can&rsquo;t possibly cover every error scenario in a setup involving several components, it covers the most common scenarios, and should address all possible security problems.</p>
<p>In our next blog post, we&rsquo;ll focus on a practical, minimal development example:<br>
while currently only the provided command line tools support OAuth, <code>libpq</code> already has the infrastructure in it to implement custom OAuth logic, allowing users to integrate it into their applications &ndash; we&rsquo;ll provide examples how it is doable.</p>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/04/30/oidc-error-scenarios/">OIDC error scenarios</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>XtraBackup incremental prepare phase is 2x-3x faster!</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/xtrabackup-incremental-prepare-phase-is-2x-3x-faster/" />
      <id>https://www.percona.com/blog/xtrabackup-incremental-prepare-phase-is-2x-3x-faster/</id>
      <updated>2026-04-29T18:52:44+00:00</updated>
      <author><name>Satya Bodapati</name></author>
      <summary type="html"><![CDATA[<p>TL;DR Percona XtraBackup is a 100% open-source backup solution for Percona Server for MySQL and MySQL®. It is designed for high-availability environments, performing online, non-blocking, and highly secure backups of transactional systems without interrupting your production traffic. While full backups work for small databases, large-scale systems rely on incremental backups to save space and time. … Continued<br />
The post XtraBackup incremental prepare phase is 2x-3x faster! appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/xtrabackup-incremental-prepare-phase-is-2x-3x-faster/">XtraBackup incremental prepare phase is 2x-3x faster!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<h2><b>TL;DR</b><a class="anchor-link" id="tldr"></a></h2>
<p><b>Percona XtraBackup</b> is a 100% open-source backup solution for Percona Server for MySQL and MySQL&reg;. It is designed for high-availability environments, performing online, non-blocking, and highly secure backups of transactional systems without interrupting your production traffic.</p>
<p>While <a href="https://docs.percona.com/percona-xtrabackup/8.0/create-full-backup.html">full backups</a> work for small databases, large-scale systems rely on <a href="https://docs.percona.com/percona-xtrabackup/8.0/create-incremental-backup.html"><b>incremental backups</b></a> to save space and time. However, the &ldquo;prepare&rdquo; stage, required to make the incremental backups consistent, was slow because XtraBackup processed the .delta files serially. The .delta files are generated per table and store only the modifications since the last backup.</p>
<p>Great news! In XtraBackup versions <span style="color: #000000"><b>8.0.35-33</b></span> and <span style="color: #000000"><b>8.4.0-3</b></span> and later, we&rsquo;ve added support for the <code><a href="https://docs.percona.com/percona-xtrabackup/8.0/xtrabackup-option-reference.html#parallel">--parallel</a></code> option during the prepare stage. This option lets XtraBackup process multiple .delta files simultaneously, <i>significantly</i> reducing the preparation time, especially when you have a large number of IBD files.</p>
<p>Please add <code><i><strong>--parallel=X</strong></i></code>, with the number of threads to use, to the <code><i><strong>xtrabackup --prepare --apply-log-only</strong></i></code> command to speed up the<a href="https://docs.percona.com/percona-xtrabackup/8.0/prepare-incremental-backup.html#faster-prepare-step-with-parallel"> incremental prepare</a> operation.</p>
<h2><b>The Incremental Backup Workflow</b><a class="anchor-link" id="the-incremental-backup-workflow"></a></h2>
<p>Before we dive into the performance gains, it&rsquo;s important to understand how Incremental backups work.</p>
<h3><b>1. Creating the Backups</b><a class="anchor-link" id="1-creating-the-backups"></a></h3>
<p>The process starts with a full backup followed by a backup that captures the changes since the last backup. This smaller backup is called an incremental backup. XtraBackup creates .delta files during incremental backups. Let&rsquo;s review an example.</p>
<ul>
<li><b>Take Full Backup:</b> Your starting point is Point A. This backup is an entire copy of your data.</li>
<li><b>Take Inc1 Backup:</b> XtraBackup identifies the changes between Point A and Point B. It creates a .delta file for every table that has been changed. Delta files contain only the pages that changed between the backups.</li>
<li><b>Take Inc2 Backup:</b> XtraBackup identifies the changes between Point B and Point C. It creates a new set of .delta files for this specific period.</li>
</ul>
<p>For more detailed steps/commands, please check the documentation here: <a href="https://docs.percona.com/percona-xtrabackup/8.0/create-incremental-backup.html">https://docs.percona.com/percona-xtrabackup/8.0/create-incremental-backup.html</a></p>
<h3><b>2. Preparing the Backups</b><a class="anchor-link" id="2-preparing-the-backups"></a></h3>
<p>To restore the data to the latest point, you must merge these changes back into the full backup. The &ldquo;prepare&rdquo; phase works differently here:</p>
<ul>
<li><b>Prepare Inc1:</b> You merge the Inc1 changes into the full backup using the <code><i>--apply-log-only</i></code> option. In this step, XtraBackup applies the .delta files and the redo logs, but does <b>not</b> apply the Undo logs</li>
<li><b>Prepare Inc2:</b> You merge the Inc2 changes into the updated base using the <code><i>--apply-log-only</i></code> option. XtraBackup applies the .delta files and the Redo logs but skips the Undo logs.</li>
<li><b>Final Prepare:</b> After all the incremental backups are merged, you run a final prepare command on the full backup. This final step applies the <b>Undo logs</b> to make the entire dataset consistent. If you apply the Undo logs during the intermediate steps, you cannot merge any further backups.</li>
</ul>
<p>More detailed steps to prepare an incremental backup are described here: <a href="https://docs.percona.com/percona-xtrabackup/8.0/prepare-incremental-backup.html">https://docs.percona.com/percona-xtrabackup/8.0/prepare-incremental-backup.html</a></p>
<h2><b>The Improvement: Parallel Incremental Delta Apply</b><a class="anchor-link" id="the-improvement-parallel-incremental-delta-apply"></a></h2>
<p>We have improved the <b>Incremental Delta Apply</b> phase. These are &ldquo;<b>Prepare inc1&rdquo;</b> and &ldquo;<b>Prepare inc2</b>&rdquo; phases as described above. <code><a href="https://docs.percona.com/percona-xtrabackup/8.0/xtrabackup-option-reference.html#parallel">--parallel</a></code> option should be used along with the <code>--apply-log-only</code> to apply the .delta files in parallel.</p>
<p>We completed this essential improvement as part of <b>[</b><a href="https://perconadev.atlassian.net/browse/PXB-3427"><b>PXB-3427</b></a><b>]</b>.</p>
<p>In previous versions, XtraBackup applied the .delta files as soon as a file was discovered in the incremental backup directory. Starting with versions <b>8.0.35-33</b> and <b>8.4.0-3</b>, to apply the .delta files, XtraBackup scans the backup directory and builds a queue of delta files. Multiple threads (defined by <code><a href="https://docs.percona.com/percona-xtrabackup/8.0/xtrabackup-option-reference.html#parallel">--parallel</a></code> ) consume this queue simultaneously. Each thread reads a .delta file and writes its pages to the corresponding InnoDB Data File (.ibd file).</p>
<h2><b>Benchmarks</b><a class="anchor-link" id="benchmarks"></a></h2>
<p>This benchmark is created using the scripts, and the instructions are in JIRA: <a href="https://perconadev.atlassian.net/browse/PXB-3427?focusedCommentId=454634">PXB-3427</a></p>
<p><img decoding="async" loading="lazy" class="alignnone size-large wp-image-44624" src="https://www.percona.com/wp-content/uploads/2026/04/prepare_performance_plot-1024x640.png" alt="xtrabackup prepare performance" width="1024" height="640"></p>
<p>When your backup contains a large number of small .delta files, increasing the <code><a href="https://docs.percona.com/percona-xtrabackup/8.0/xtrabackup-option-reference.html#parallel">--parallel</a></code> value can drastically reduce the time taken to prepare the incremental backup by distributing the high per-file overhead across more threads. However, for other categories with fewer or larger files, performance typically plateaus after <b>16 threads</b>, and pushing higher can even lead to slight regressions due to thread management overhead. While there is no single &ldquo;golden value&rdquo; to recommend for every scenario, we recommend starting with a value of <b>8</b> to find the optimal balance for your specific environment.</p>
<h2><b>Disk Utilization with XtraBackup prepare using <code>--parallel=1</code> vs <code>--parallel=64</code></b><a class="anchor-link" id="disk-utilization-with-xtrabackup-prepare-using-parallel1-vs-parallel64"></a></h2>
<p>The <a href="https://www.percona.com/monitoring/">PMM</a> graphs below show the Disk IOPs used by the XtraBackup prepare command. The graph is generated when XtraBackup applies the incremental backup to a full backup directory. Incremental backup directory that has 20,608 .delta files, each of which is 2.5 MB.</p>
<h3><b>With <code>--parallel=1</code></b><a class="anchor-link" id="with-parallel1"></a></h3>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-44625" src="https://www.percona.com/wp-content/uploads/2026/04/parallel_00.png" alt="xtrabackup incremental disk IOPs with --parllel=1" width="713" height="392"></p>
<p>With <code>--parallel=1</code>, max Disk IOPs utilized is 18.2 K, and the XtraBackup prepare operation finished in 3.76 minutes.</p>
<h3><b>With <code>--parallel=64</code></b><b><br>
</b><a class="anchor-link" id="with-parallel64"></a></h3>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-44626" src="https://www.percona.com/wp-content/uploads/2026/04/parallel_64_11.png" alt="xtrabackup incremental delta prepare performance with parallel 64" width="719" height="404"></p>
<p>&nbsp;</p>
<p>With <code><strong>--parallel=64</strong></code>, the max Disk Write IOPs utilized is 85K, and the XtraBackup prepare operation finished in around a minute. XtraBackup utilized<b> 4.67x</b> more disk IOPS and finished <b>3.49x</b> faster.</p>
<h2><b>Results from the bug reporter</b><a class="anchor-link" id="results-from-the-bug-reporter"></a></h2>
<p>We saw some amazing results shared by the reporter on <a href="https://perconadev.atlassian.net/browse/PXB-3427">PXB-3427</a>.&nbsp; The time required for XtraBackup prepare command (<code>--prepare --apply-log-only</code>)&nbsp; to complete, reduced from <b>237 minutes to just 6 minutes</b>. That&rsquo;s an incredible <b>40X speed-up</b>!</p>
<p>Here are the details from their setup:</p>
<ul>
<li><b>Full backup:</b> 235,188 *.ibd files</li>
<li><b>Incremental backup:</b> 236,214 *.ibd.delta files</li>
<li><b>Average </b><b>.delta</b><b> size:</b> 53,041 bytes (~53KB)</li>
<li><b>Threads used:</b> 48 (&ndash;parallel=48)</li>
<li><b>Disk specs:</b> 25K IOPS performance and an average of 500 to 600 MB/s of throughput</li>
</ul>
<p><b>We hear you!</b> This specific feature came to us from a post on the community <a href="https://forums.percona.com/t/mysql-8-4-disabling-innodb-redo-log-during-restore-safe-practice/35675">forum</a>. We reached out, asked them to create a JIRA ticket, and then implemented the improvement. We wanted to share this story as a demonstration of our commitment to listening to and acting on community feedback!</p>
<p>The post <a href="https://www.percona.com/blog/xtrabackup-incremental-prepare-phase-is-2x-3x-faster/">XtraBackup incremental prepare phase is 2x-3x faster!</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/xtrabackup-incremental-prepare-phase-is-2x-3x-faster/">XtraBackup incremental prepare phase is 2x-3x faster!</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Orchestrator’s Next Chapter: What It Means for Percona Customers</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/orchestrators-next-chapter-what-it-means-for-percona-customers/" />
      <id>https://www.percona.com/blog/orchestrators-next-chapter-what-it-means-for-percona-customers/</id>
      <updated>2026-04-29T17:55:10+00:00</updated>
      <author><name>Dennis Kittrell</name></author>
      <summary type="html"><![CDATA[<p>Last week, ProxySQL announced that they are taking over the maintenance and development of Orchestrator, the MySQL high-availability and topology management tool originally authored by Shlomi Noach. You can read their announcement here: Announcing the future of Orchestrator. We want to briefly share Percona’s position on the news. We welcome this Orchestrator became the de … Continued<br />
The post Orchestrator’s Next Chapter: What It Means for Percona Customers appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/orchestrators-next-chapter-what-it-means-for-percona-customers/">Orchestrator’s Next Chapter: What It Means for Percona Customers</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Last week, ProxySQL announced that they are taking over the maintenance and development of Orchestrator, the MySQL high-availability and topology management tool originally authored by Shlomi Noach. You can read their announcement here: <a href="https://proxysql.com/blog/announcing-proxysql-takes-over-orchestrator/" target="_blank" rel="noopener">Announcing the future of Orchestrator</a>.</p>
<p>We want to briefly share Percona&rsquo;s position on the news.</p>
<h4>We welcome this</h4>
<p>Orchestrator became the de facto standard for MySQL topology management and automated failover, and it has been a foundational tool in the ecosystem for over a decade. When the upstream project was archived, many operators were left running internal forks. A revived project under active development, with a stated roadmap and continued Apache 2.0 licensing, is good news for the MySQL community, and we&rsquo;re glad to see ProxySQL step up to take it on. Thanks are due to Shlomi Noach for creating Orchestrator in the first place, and to everyone who contributed to it over the years.</p>
<h4>A small clarification on Percona&rsquo;s role</h4>
<p>The ProxySQL announcement kindly credited Percona alongside GitHub for &ldquo;stewardship over the years.&rdquo; To be accurate: Percona has never been a maintainer of the upstream Orchestrator project. What we have done, and will continue to do, is support our customers who rely on it. That includes operational guidance, troubleshooting, and carrying internal patches where a customer situation requires it. The upstream project itself has always lived with Shlomi and later with the team at GitHub.</p>
<h4>Nothing changes for Percona customers</h4>
<p>If you are a Percona customer running Orchestrator today, your support experience is unchanged. We will continue helping you operate it in production, diagnose issues, and plan around its role in your high-availability stack. That commitment is steady regardless of where the upstream project lives.</p>
<p>Orchestrator&rsquo;s maintenance also matters to us beyond support engagements. Percona Operator for MySQL uses Orchestrator to manage asynchronous topologies, so our own product depends on the project staying healthy. That&rsquo;s part of why we plan to coordinate closely with the ProxySQL team as the next chapter unfolds.</p>
<h4>Coordinating with the ProxySQL team</h4>
<p>We plan to open coordination conversations with the ProxySQL team to make sure that operators running Orchestrator today, including our customers, have a smooth path as the project evolves. We wish the ProxySQL team well in this next chapter and look forward to supporting the community alongside them.</p>
<p>If you&rsquo;re a Percona customer, reach out to your account team with any questions about your Orchestrator deployment. If you&rsquo;re running Orchestrator outside of a Percona engagement and want to talk through support options, <a href="https://www.percona.com/mysql/support/">get in touch with our MySQL team</a>.</p>
<p>&nbsp;</p>
<p><!-- notionvc: 1a4652ac-6c9a-433e-8656-d58627a32d40 --></p>
<p>The post <a href="https://www.percona.com/blog/orchestrators-next-chapter-what-it-means-for-percona-customers/">Orchestrator&rsquo;s Next Chapter: What It Means for Percona Customers</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/orchestrators-next-chapter-what-it-means-for-percona-customers/">Orchestrator’s Next Chapter: What It Means for Percona Customers</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Adding a New Data Type to MariaDB with Type_handler – Part 0</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-0/" />
      <id>https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-0/</id>
      <updated>2026-04-29T09:37:01+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>Welcome to this new series about extending MariaDB. This series covers the addition of a new data type using the Type_handler.<br />
The goal of the entire series is to create a new plugin data type MONEY to store and display amounts with currency. …<br />
Continue reading \"Adding a New Data Type to MariaDB with Type_handler – Part 0\"<br />
The post Adding a New Data Type to MariaDB with Type_handler – Part 0 appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-0/">Adding a New Data Type to MariaDB with Type_handler – Part 0</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Welcome to this new series about extending MariaDB. This series covers the addition of a new data type using the <a href="https://www.linkedin.com/pulse/mariadbs-pluggable-data-type-framework-deep-dive-drrtuy-drrtuy-p6yke/">Type_handler</a>.<br>
The goal of the entire series is to create a new plugin data type MONEY to store and display amounts with currency. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-0/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Adding a New Data Type to MariaDB with Type_handler &ndash; Part 0&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-0/">Adding a New Data Type to MariaDB with Type_handler &ndash; Part 0</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-0/">Adding a New Data Type to MariaDB with Type_handler – Part 0</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>pgBackRest is archived, what now?</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/04/28/pgbackrest-is-archived-what-now/" />
      <id>https://percona.community/blog/2026/04/28/pgbackrest-is-archived-what-now/</id>
      <updated>2026-04-28T11:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>pgBackRest is an open source backup and restore tool for PostgreSQL. It’s fair to say it’s one of the most popular options, widely used across the PostgreSQL ecosystem.</p>
<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/04/28/pgbackrest-is-archived-what-now/">pgBackRest is archived, what now?</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><a href="https://github.com/pgbackrest/pgbackrest" target="_blank" rel="noopener noreferrer">pgBackRest</a> is an open source backup and restore tool for PostgreSQL. It&rsquo;s fair to say it&rsquo;s one of the most popular options, widely used across the PostgreSQL ecosystem.</p>
<p>On 27 April 2026, pgBackRest maintainer David Steele announced on <a href="https://www.linkedin.com/posts/davidsteele_after-a-lot-of-thought-i-have-decided-to-share-7454442611911655424-mVMS?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAAAD3qpgBKSXefFXDYJlyIbIdar9mZh-NYBw" target="_blank" rel="noopener noreferrer">LinkedIn</a> and in the <a href="https://github.com/pgbackrest/pgbackrest" target="_blank" rel="noopener noreferrer">GitHub repository</a> that the project is becoming <del>unmaintained</del> archived, starting with:</p>
<blockquote>
<p>TL;DR: pgBackRest is no longer being maintained. If you fork pgBackRest, please select a new name for your project.</p>
</blockquote>
<div style="width:70%;margin: auto">
<p><figure><img decoding="async" src="https://percona.community/blog/2026/04/Jan-david-li.png" alt="&nbsp;"></figure>
</p>
</div>
<p>If you&rsquo;re reading this, you&rsquo;re likely either affected or at least concerned. In this short write up I will do my best to calm your nerves, present short term as well as more long term ideas and options.</p>
<h2 id="where-are-we-now---the-status-quo">Where are we now &ndash; the status quo<a class="anchor-link" id="where-are-we-now-the-status-quo"></a></h2>
<p>pgBackRest is a critical part of the PostgreSQL ecosystem, and nobody seriously expects it to simply disappear. What happens next is now up to the community.<br>
One possible outcome is the emergence of multiple forks of pgBackRest. That raises the risk of fragmentation or, put bluntly, <del>Clone</del> Fork Wars.</p>
<div style="width:70%;margin: auto">
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/04/Jan-forks.png" alt="&nbsp;"></figure>
</p>
</div>
<p>That said, there has already been a significant amount of discussion across the community, and one thing is clear:</p>
<p>The PostgreSQL community acknowledges the problem and wants change.</p>
<p>The challenge now is twofold:</p>
<ul>
<li>What can we do immediately to stabilize the situation?</li>
<li>What direction should we take long term, without overcomplicating the short-term response?</li>
</ul>
<h2 id="what-is-percona-planning">What is Percona planning<a class="anchor-link" id="what-is-percona-planning"></a></h2>
<p><a href="https://docs.percona.com/postgresql/14/solutions/backup-recovery.html#pgbackrest" target="_blank" rel="noopener noreferrer">Percona includes pgBackRest</a> in the <a href="https://docs.percona.com/postgresql/14/index.html" target="_blank" rel="noopener noreferrer">Percona Distribution for PostgreSQL</a> as the recommended backup and restore solution. From our perspective, it remains the most mature, enterprise-ready and reliable option available. While alternatives like WAL-G or Barman are well regarded, our recommendation remains unchanged.</p>
<p>To emphasize the message:</p>
<blockquote>
<p>the current situation does <u>not</u> impact our recommendation.</p>
</blockquote>
<p>Percona will continue supporting pgBackRest. What that support looks like in terms of maintainership and collaboration with other organizations is still being actively discussed and will take time to solidify.</p>
<p>The immediate priority is to avoid fragmentation. We want to ensure we don&rsquo;t end up with multiple independent forks maintained in isolation.</p>
<p>If you are a Percona customer, you remain fully supported. Please continue reporting issues through standard support channels. For our community users, we encourage you to use the <a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Percona Community Forums</a>, we will do our best to help there.</p>
<h2 id="the-power-of-open-source-community">The power of open source community<a class="anchor-link" id="the-power-of-open-source-community"></a></h2>
<p>In an era where we often hear about companies reducing teams due to AI-driven cost optimization, it&rsquo;s easy to forget that software is still built and maintained by people. This is especially true in open source.</p>
<p>Two observations are worth calling out:</p>
<ol>
<li>People need sustainable funding, work cannot be assumed to be purely voluntary.</li>
<li>A healthy open source project should not depend on a single company or individual.</li>
</ol>
<p>The current situation is, to some extent, a result of the opposite model. pgBackRest development was largely driven by a single company and later single maintainer, <a href="https://github.com/dwsteele" target="_blank" rel="noopener noreferrer">David Steele</a>, with sponsorship from Crunchy Data. While others have contributed (e.g.i <a href="https://github.com/sfrost" target="_blank" rel="noopener noreferrer">Stephen Frost</a> and Stefan Fercot &ndash; <a href="https://github.com/pgstef" target="_blank" rel="noopener noreferrer">pgstef</a>), and there was a wider team maintaining the project in the past, recently the project effectively relied on one primary maintainer.</p>
<p>I think it&rsquo;s fair to say we&rsquo;ve seen a fair share <a href="https://xkcd.com/2347/" target="_blank" rel="noopener noreferrer">xkcd #2347</a> posted all over the internet over the course of last 24h. So here&rsquo;s one more:</p>
<div style="width:50%;margin: auto">
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/04/Jan-comic-neb.png" alt="&nbsp;"></figure>
</p>
</div>
<p>To avoid repeating this pattern, we (along with other vendors) are deliberately taking time before jumping into forks or immediate solutions. The goal is to find a sustainable, collaborative model rather than rushing into fragmentation.</p>
<p>For comparison, it took the Linux Foundation 6 days to respond to the <a href="https://github.com/redis/redis/pull/13157" target="_blank" rel="noopener noreferrer">Redis license change</a> by <a href="https://www.linuxfoundation.org/press/linux-foundation-launches-open-source-valkey-community" target="_blank" rel="noopener noreferrer">launching Valkey</a>. While this situation is different as there&rsquo;s no license change in pgBackRest, it illustrates that meaningful coordination takes time.</p>
<p>This is exactly where the open source community can demonstrate its strength.</p>
<h2 id="what-are-the-long-term-options">What are the long term options?<a class="anchor-link" id="what-are-the-long-term-options"></a></h2>
<p>This situation is particularly surprising to me personally, as I recently referenced David&rsquo;s proposed transparent funding model in <a href="https://www.postgresql.eu/events/pgconfde2026/" target="_blank" rel="noopener noreferrer">my talk</a> at <a href="http://pgconf.de/" target="_blank" rel="noopener noreferrer">PGConf.DE</a> just last week.</p>
<div style="width:30%;margin: auto">
<p><figure><img decoding="async" src="https://percona.community/blog/2026/04/Jan-david-money.png" alt="&nbsp;"></figure>
</p>
</div>
<p>The idea, distributing funding across organizations that rely on the project, seemed like a promising path toward a more sustainable ecosystem. In hindsight, it appears that adoption of this model was either too slow or insufficient to support ongoing maintenance.</p>
<p>Looking ahead, several long-term options are being discussed within the community:</p>
<ul>
<li>Establishing a foundation-backed project (similar to models used by <a href="https://codeberg.org/" target="_blank" rel="noopener noreferrer">Codeberg</a> or the Linux Foundation)</li>
<li>Creating a coordinated, multi-vendor stewardship model</li>
<li>In more extreme scenarios, moving critical tooling closer to the PostgreSQL core ecosystem</li>
</ul>
<p>These discussions are ongoing. If you&rsquo;re attending <a href="https://2026.pgconf.dev/" target="_blank" rel="noopener noreferrer">PGConf.Dev</a>, this will almost certainly be a major topic, especially in the extensions ecosystem track of community sessions in the <a href="https://2026.pgconf.dev/schedule/tuesday" target="_blank" rel="noopener noreferrer">Canfor</a> room on Tuesday.</p>
<h2 id="so-what-should-i-do-now">So what should I do now?<a class="anchor-link" id="so-what-should-i-do-now"></a></h2>
<div style="width:70%;margin: auto">
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/04/Jan-what-now.png" alt="&nbsp;"></figure>
</p>
</div>
<p>In short, nothing but wait. Yes, this means:</p>
<blockquote>
<p>Keep on using pgBackRest as you did!</p>
</blockquote>
<p>If your company is relying on pgBackRest, now is the time to engage. If you have capacity for this, please join the discussion (we&rsquo;ve kicked off a thread on <a href="https://forums.percona.com/t/pgbackrest-archival-discussion/40725?u=jan_wieremjewicz" target="_blank" rel="noopener noreferrer">Percona Community Forums</a> if you are looking for a place to join this topic)</p>
<p>Rest assured that you can follow the updates from us, we will be messaging about the progress made in regards to establishing the future for pgBackRest.</p>
<p>One thing to clear is: are there any immediate risks?</p>
<blockquote>
<p>Not new ones. There is the uncertainty that this is not a comfortable feeling. Rest assured that the longevity of the solution is not in jeopardy as we do have an obligation to our customer and user base to make sure the project is continued.</p>
</blockquote>

<p>The post <a rel="nofollow" href="https://percona.community/blog/2026/04/28/pgbackrest-is-archived-what-now/">pgBackRest is archived, what now?</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Database Trends: What is changing in the database world (besides AI)</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/database-trends-what-is-changing-in-the-database-world-besides-ai/" />
      <id>https://mariadb.org/database-trends-what-is-changing-in-the-database-world-besides-ai/</id>
      <updated>2026-04-27T13:48:45+00:00</updated>
      <author><name>Kaj Arnö</name></author>
      <summary type="html"><![CDATA[<p>Earlier this month, I had a half-hour chat with Kellyn Gorman, a Database and AI Advocate and Engineer at Redgate. …<br />
Continue reading \"Database Trends: What is changing in the database world (besides AI)\"<br />
The post Database Trends: What is changing in the database world (besides AI) appeared first on MariaDB.org.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/database-trends-what-is-changing-in-the-database-world-besides-ai/">Database Trends: What is changing in the database world (besides AI)</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>Earlier this month, I had a half-hour chat with <a href="https://www.red-gate.com/blog/author/kellyn-gorman/" target="_blank" rel="noreferrer noopener">Kellyn Gorman</a>, a Database and AI Advocate and Engineer at <a href="https://www.red-gate.com/" target="_blank" rel="noreferrer noopener">Redgate</a>. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/database-trends-what-is-changing-in-the-database-world-besides-ai/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Database Trends: What is changing in the database world (besides AI)&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/database-trends-what-is-changing-in-the-database-world-besides-ai/">Database Trends: What is changing in the database world (besides AI)</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p>The post <a rel="nofollow" href="https://mariadb.org/database-trends-what-is-changing-in-the-database-world-besides-ai/">Database Trends: What is changing in the database world (besides AI)</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Achieving High Availability with Valkey Sentinel</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/achieving-high-availability-with-valkey-sentinel/" />
      <id>https://www.percona.com/blog/achieving-high-availability-with-valkey-sentinel/</id>
      <updated>2026-04-24T04:03:16+00:00</updated>
      <author><name>Arunjith Aravindan</name></author>
      <summary type="html"><![CDATA[<p>In the previous guide, a robust Primary-Replica topology for Valkey was established. Read scaling is now active, and a hot copy of the data is securely stored on a second node. But there is a catch. If a primary node crashes, the replica will remain faithful and wait for instructions. It will not automatically take … Continued<br />
The post Achieving High Availability with Valkey Sentinel appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/achieving-high-availability-with-valkey-sentinel/">Achieving High Availability with Valkey Sentinel</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">In the previous </span><a href="https://www.percona.com/blog/scaling-your-cache-a-step-by-step-guide-to-setting-up-valkey-replication/"><span style="font-weight: 400">guid</span></a><span style="font-weight: 400">e, a robust Primary-Replica topology for Valkey was established. Read scaling is now active, and a hot copy of the data is securely stored on a second node.</span></p>
<p><span style="font-weight: 400">But there is a catch. If a primary node crashes, the replica will remain faithful and wait for instructions. It will not automatically take over the responsibilities of the primary. Applications will start throwing write errors until an administrator manually logs in and reconfigures the replica to become the new primary.</span></p>
<p><span style="font-weight: 400">To achieve true High Availability (HA) and ensure continuous uptime without manual intervention, </span><b>Valkey Sentinel</b><span style="font-weight: 400"> is required.</span></p>
<h2><b>What is Valkey Sentinel?</b><a class="anchor-link" id="what-is-valkey-sentinel"></a></h2>
<p><span style="font-weight: 400">Valkey Sentinel is a distributed system designed to monitor Valkey instances, detect failures, and automatically handle failover.</span></p>
<p><span style="font-weight: 400">When Sentinel detects that a primary node is unresponsive, it performs the following tasks:</span></p>
<ol>
<li style="font-weight: 400"><b>Monitoring:</b><span style="font-weight: 400"> It continuously checks whether primary and replica nodes are functioning as expected.</span></li>
<li style="font-weight: 400"><b>Notification:</b><span style="font-weight: 400"> It can notify system administrators or another computer program via an API that something is wrong.</span></li>
<li style="font-weight: 400"><b>Automatic Failover:</b><span style="font-weight: 400"> It promotes a healthy replica to the new primary and reconfigures the other replicas to sync with it.</span></li>
<li style="font-weight: 400"><b>Configuration Provider:</b><span style="font-weight: 400"> It acts as a source of truth for clients. Applications can connect to Sentinel to ask for the current primary&rsquo;s address. If a failover occurs, Sentinel reports the new address.</span></li>
</ol>
<h2><b>The Rule of Three (Quorum)</b><a class="anchor-link" id="the-rule-of-three-quorum"></a></h2>
<p><span style="font-weight: 400">Sentinel is a distributed system, meaning multiple Sentinel processes must run and agree on a node&rsquo;s failure before taking action. This agreement is called a </span><b>quorum</b><span style="font-weight: 400">.</span></p>
<p><span style="font-weight: 400">To prevent a &ldquo;split-brain&rdquo; scenario (where a network partition causes two nodes to both assume they are the primary), </span><b>at least three Sentinel instances</b><span style="font-weight: 400"> must be deployed.</span></p>
<p><span style="font-weight: 400">For this guide, the environment consists of three dedicated database nodes. Each node will run both the Valkey database service and the Valkey Sentinel service:</span></p>
<ul>
<li style="font-weight: 400"><b>ArunValkeyPrimary (Primary + Sentinel):</b> <span style="font-weight: 400">172.31.32.27</span></li>
<li style="font-weight: 400"><b>ArunValkeyReplica (Replica 1 + Sentinel):</b> <span style="font-weight: 400">172.31.37.55</span></li>
<li style="font-weight: 400"><b>ArunValkeyReplica2 (Replica 2 + Sentinel):</b> <span style="font-weight: 400">172.31.39.58</span></li>
</ul>
<p><span style="font-weight: 400">The primary node is healthy and running as the master, with two replicas connected and actively syncing.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@ArunValkeyPrimary:/home/ubuntu# valkey-cli -a amma@123

Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.

127.0.0.1:6379&gt; INFO replication

# Replication

role:master

connected_slaves:2

slave0:ip=172.31.37.55,port=6379,state=online,offset=98214,lag=1

slave1:ip=172.31.39.58,port=6379,state=online,offset=98214,lag=1

master_failover_state:no-failover

master_replid:629656a198b7290bf6492e470b449ad1ced509e0

master_replid2:30977276632877f46ad12fcc2bbc2c5191c67c0c

master_repl_offset:98214

second_repl_offset:1643

repl_backlog_active:1

repl_backlog_size:1048576

repl_backlog_first_byte_offset:1643

repl_backlog_histlen:96572

127.0.0.1:6379&gt;</pre>

<h2><b>Step 1: Create the Sentinel Configuration File</b><a class="anchor-link" id="step-1-create-the-sentinel-configuration-file"></a></h2>
<p><span style="font-weight: 400">Sentinel runs as a separate process from the main Valkey database, using its own configuration file and listening on port </span><span style="font-weight: 400">26379</span><span style="font-weight: 400"> by default.</span></p>
<p><span style="font-weight: 400">The Sentinel configuration file (typically </span><span style="font-weight: 400">/etc/valkey/sentinel.conf</span><span style="font-weight: 400">) must be created or edited on </span><b>all three nodes</b><span style="font-weight: 400">(</span><span style="font-weight: 400">ArunValkeyPrimary</span><span style="font-weight: 400">, </span><span style="font-weight: 400">ArunValkeyReplica</span><span style="font-weight: 400">, and </span><span style="font-weight: 400">ArunValkeyReplica2</span><span style="font-weight: 400">).</span></p>
<p><span style="font-weight: 400">Open the file and add the following core directives:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">port 26379
# Format: sentinel monitor &lt;cluster-name&gt; &lt;primary-ip&gt; &lt;primary-port&gt; &lt;quorum&gt;
sentinel monitor mymaster 172.31.32.27 6379 2

# The primary password set in the previous setup
sentinel auth-user mymaster default

sentinel auth-pass mymaster amma@123

# How many milliseconds the primary must be unreachable before Sentinel considers it down
sentinel down-after-milliseconds mymaster 5000

# How long to wait before trying another failover if the first one fails
sentinel failover-timeout mymaster 10000</pre>
<p><b>Understanding the </b><b>monitor</b><b> line:</b></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">mymaster</span><span style="font-weight: 400"> is the arbitrary name given to this cluster.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">172.31.32.27 6379</span><span style="font-weight: 400"> points to the current primary node (</span><span style="font-weight: 400">ArunValkeyPrimary</span><span style="font-weight: 400">). (Sentinels will automatically discover both replicas by querying the primary, so the replica IPs do not need to be listed).</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">2</span><span style="font-weight: 400"> is the quorum. This means at least 2 out of the 3 Sentinels must agree the primary is down to initiate a failover.</span></li>
</ul>
<h2><b>Step 2: Ensure Proper Permissions</b><a class="anchor-link" id="step-2-ensure-proper-permissions"></a></h2>
<p><span style="font-weight: 400">Sentinel needs the ability to rewrite its own configuration file. When a failover happens, Sentinel updates </span><span style="font-weight: 400">sentinel.conf</span><span style="font-weight: 400"> with the new primary&rsquo;s IP address and the current state of the cluster.</span></p>
<p><span style="font-weight: 400">Ensure the </span><span style="font-weight: 400">valkey</span><span style="font-weight: 400"> user has write permissions to the file on all three nodes:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">sudo chown valkey:valkey /etc/valkey/sentinel.conf</pre>

<h2><b>Step 3: Start the Sentinel Services</b><a class="anchor-link" id="step-3-start-the-sentinel-services"></a></h2>
<p><span style="font-weight: 400">Start the Sentinel service on all three nodes. Depending on the Linux distribution and the Valkey installation method, this is usually done via </span><span style="font-weight: 400">systemctl</span><span style="font-weight: 400">:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@ArunValkeyPrimary:/home/ubuntu# sudo systemctl enable valkey-sentinel
Synchronizing state of valkey-sentinel.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install enable valkey-sentinel
root@ArunValkeyPrimary:/home/ubuntu# sudo systemctl start valkey-sentinel
root@ArunValkeyPrimary:/home/ubuntu#


root@ArunValkeyReplica:/home/ubuntu# sudo systemctl enable valkey-sentinel
Synchronizing state of valkey-sentinel.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install enable valkey-sentinel
root@ArunValkeyReplica:/home/ubuntu# sudo systemctl start valkey-sentinel
root@ArunValkeyReplica:/home/ubuntu#

root@ArunValkeyReplica2:/home/ubuntu# sudo systemctl enable valkey-sentinel
Synchronizing state of valkey-sentinel.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install enable valkey-sentinel
root@ArunValkeyReplica2:/home/ubuntu# sudo systemctl start valkey-sentinel
root@ArunValkeyReplica2:/home/ubuntu#</pre>

<h2><b>Step 4: Verify the Sentinel Cluster</b><a class="anchor-link" id="step-4-verify-the-sentinel-cluster"></a></h2>
<p><span style="font-weight: 400">Check if the Sentinels are successfully communicating with each other and monitoring the database. Log into any node and use the Valkey CLI to connect to the Sentinel port (</span><span style="font-weight: 400">26379</span><span style="font-weight: 400">):</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@ArunValkeyPrimary:/home/ubuntu# valkey-cli -p 26379
AUTH failed: ERR AUTH &lt;password&gt; called without any password configured for the default user. Are you sure your configuration is correct?
127.0.0.1:26379&gt; INFO sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_tilt_since_seconds:-1
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=172.31.32.27:6379,slaves=2,sentinels=3
127.0.0.1:26379&gt;</pre>
<p><span style="font-weight: 400">Look closely at the </span><span style="font-weight: 400">master0</span><span style="font-weight: 400"> line at the bottom. This confirms everything is functioning correctly:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">status=ok</span><span style="font-weight: 400">: The primary (</span><span style="font-weight: 400">ArunValkeyPrimary</span><span style="font-weight: 400">) is healthy.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">slaves=2</span><span style="font-weight: 400">: Sentinel found both </span><span style="font-weight: 400">ArunValkeyReplica</span><span style="font-weight: 400"> and </span><span style="font-weight: 400">ArunValkeyReplica2</span><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">sentinels=3</span><span style="font-weight: 400">: All three Sentinel instances have discovered each other and formed a quorum.</span></li>
</ul>
<h4><b>Additional Verification: Sentinel Peer Health</b></h4>
<p><span style="font-weight: 400">To further validate that all Sentinel nodes are actively communicating and healthy, we can query the list of Sentinel peers and inspect their status:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@ArunValkeyPrimary:/home/ubuntu# valkey-cli -p 26379 SENTINEL SENTINELS mymaster | grep -E -A 1 '^ip$|^flags$|^last-ok-ping-reply$|^down-after-milliseconds$'
ip
172.31.37.55
--
flags
sentinel
--
last-ok-ping-reply
65
--
down-after-milliseconds
5000
--
ip
172.31.39.58
--
flags
sentinel
--
last-ok-ping-reply
65
--
down-after-milliseconds
5000
root@ArunValkeyPrimary:/home/ubuntu#</pre>

<h3><b>What this means:</b><a class="anchor-link" id="what-this-means"></a></h3>
<ul>
<li style="font-weight: 400"><b>ip</b><span style="font-weight: 400"> &rarr; Lists the other Sentinel nodes in the cluster</span></li>
<li style="font-weight: 400"><b>flags=sentinel</b><span style="font-weight: 400"> &rarr; Confirms these are active Sentinel peers</span></li>
<li style="font-weight: 400"><b>last-ok-ping-reply</b><span style="font-weight: 400"> &rarr; Indicates the last successful heartbeat response (in milliseconds)</span></li>
<li style="font-weight: 400"><b>down-after-milliseconds: 5000 ms</b><span style="font-weight: 400"> &rarr; failure threshold</span></li>
</ul>
<p><span style="font-weight: 400">Lower values here indicate </span><b>healthy and responsive communication</b><span style="font-weight: 400"> between Sentinel nodes.</span></p>
<h2><b>Step 5: The Chaos Test (Triggering a Failover)</b><a class="anchor-link" id="step-5-the-chaos-test-triggering-a-failover"></a></h2>
<p><span style="font-weight: 400">The best way to trust an HA setup is to break it intentionally. We will simulate a crash by killing the primary node, verifying the failover, and then manually failing back to our original primary.</span></p>
<h3><b>1. Kill the Primary</b><a class="anchor-link" id="1-kill-the-primary"></a></h3>
<p><span style="font-weight: 400">On </span><b>ArunValkeyPrimary</b><span style="font-weight: 400"> (</span><span style="font-weight: 400">172.31.32.27</span><span style="font-weight: 400">), stop the Valkey database service (do not stop Sentinel, just the database):</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@ArunValkeyPrimary:/home/ubuntu# sudo systemctl stop valkey
root@ArunValkeyPrimary:/home/ubuntu#</pre>

<h3><b>2. Verify the Failover via Sentinel</b><a class="anchor-link" id="2-verify-the-failover-via-sentinel"></a></h3>
<p><span style="font-weight: 400">Wait for about 5 to 10 seconds to allow the </span><span style="font-weight: 400">down-after-milliseconds</span><span style="font-weight: 400"> threshold to pass and the Sentinels to complete the election process. Instead of checking the logs, you can query the Sentinel information directly to confirm the failover has occurred and find out which node was promoted.</span></p>
<p><span style="font-weight: 400">On </span><b>ArunValkeyReplica</b><span style="font-weight: 400">, connect to the Sentinel port (</span><span style="font-weight: 400">26379</span><span style="font-weight: 400">) and run the </span><span style="font-weight: 400">INFO sentinel</span><span style="font-weight: 400"> command:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@ArunValkeyReplica:/home/ubuntu# valkey-cli -p 26379
127.0.0.1:26379&gt; INFO sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_tilt_since_seconds:-1
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=172.31.37.55:6379,slaves=2,sentinels=3
127.0.0.1:26379&gt;</pre>
<p><span style="font-weight: 400">Look at the </span><span style="font-weight: 400">master0</span><span style="font-weight: 400"> line at the bottom. It shows that the status is </span><span style="font-weight: 400">ok</span><span style="font-weight: 400"> and the primary address is now </span><b>172.31.37.55:6379</b><span style="font-weight: 400">.</span></p>
<h3><b>3. Verify the Failover via the Database</b><a class="anchor-link" id="3-verify-the-failover-via-the-database"></a></h3>
<p><span style="font-weight: 400">Now, connect to that newly promoted node (</span><span style="font-weight: 400">172.31.37.55</span><span style="font-weight: 400">) on the standard database port to verify the promotion from the database&rsquo;s perspective:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@ArunValkeyReplica:/home/ubuntu# valkey-cli -a amma@123
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379&gt; INFO replication
# Replication
role:master
connected_slaves:1
slave0:ip=172.31.39.58,port=6379,state=online,offset=574633,lag=0
master_failover_state:no-failover
master_replid:b93b82982616a59a2304a799e548d7398ee15732
master_replid2:43ea3aeca4846f06c3c6dd11174e9bfd7ac7fabf
master_repl_offset:574633
second_repl_offset:475110
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:450256
repl_backlog_histlen:124378
127.0.0.1:6379&gt;</pre>
<p><span style="font-weight: 400">Notice that the </span><span style="font-weight: 400">role</span><span style="font-weight: 400"> has changed from </span><span style="font-weight: 400">slave</span><span style="font-weight: 400"> to </span><span style="font-weight: 400">master</span><span style="font-weight: 400">, and it now shows </span><span style="font-weight: 400">1</span><span style="font-weight: 400"> connected slave (the other surviving replica, </span><span style="font-weight: 400">172.31.39.58</span><span style="font-weight: 400">).</span></p>
<h3><b>4. Restarting the Old Primary</b><a class="anchor-link" id="4-restarting-the-old-primary"></a></h3>
<p><span style="font-weight: 400">When the Valkey service on </span><b>ArunValkeyPrimary</b><span style="font-weight: 400"> is eventually restarted, Sentinel will automatically detect it, reconfigure it as a read-only replica, and point it to the newly promoted primary to catch up on missed data.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@ArunValkeyPrimary:/home/ubuntu# sudo systemctl start valkey
root@ArunValkeyPrimary:/home/ubuntu# valkey-cli -p 26379 INFO sentinel
AUTH failed: ERR AUTH &lt;password&gt; called without any password configured for the default user. Are you sure your configuration is correct?
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_tilt_since_seconds:-1
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=172.31.37.55:6379,slaves=2,sentinels=3</pre>
<p><span style="font-weight: 400">Check the database replication status on the old primary to see it is now acting as a replica:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@ArunValkeyPrimary:/home/ubuntu# valkey-cli INFO replication
# Replication
role:slave
master_host:172.31.37.55
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_read_repl_offset:614120
slave_repl_offset:614120
slave_priority:1
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:b93b82982616a59a2304a799e548d7398ee15732
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:614120
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:607384
repl_backlog_histlen:6737
root@ArunValkeyPrimary:/home/ubuntu#</pre>

<h3><b>5. Executing a Manual Failback</b><a class="anchor-link" id="5-executing-a-manual-failback"></a></h3>
<p><span style="font-weight: 400">If you want </span><b>ArunValkeyPrimary</b><span style="font-weight: 400"> to reclaim its throne as the primary node, you can trigger a manual failover. First, configure it to have a high priority for elections, then issue the failover command to Sentinel:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@ArunValkeyPrimary:/home/ubuntu# valkey-cli CONFIG SET replica-priority 1
OK
root@ArunValkeyPrimary:/home/ubuntu# valkey-cli CONFIG REWRITE
OK
root@ArunValkeyPrimary:/home/ubuntu# valkey-cli -p 26379 SENTINEL FAILOVER mymaster
AUTH failed: ERR AUTH &lt;password&gt; called without any password configured for the default user. Are you sure your configuration is correct?
OK</pre>
<p><i><span style="font-weight: 400">(Note: The AUTH failed warnings simply indicate the CLI attempted to pass a default auth to a Sentinel instance that might not require it or is configured differently, but the </span></i><i><span style="font-weight: 400">OK</span></i><i><span style="font-weight: 400"> confirms the command successfully executed.)</span></i></p>
<p><span style="font-weight: 400">Check Sentinel one last time to confirm </span><span style="font-weight: 400">ArunValkeyPrimary</span><span style="font-weight: 400"> (</span><span style="font-weight: 400">172.31.32.27</span><span style="font-weight: 400">) is back in charge:</span><b></b></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@ArunValkeyPrimary:/home/ubuntu# valkey-cli -p 26379 INFO sentinel

AUTH failed: ERR AUTH &lt;password&gt; called without any password configured for the default user. Are you sure your configuration is correct?
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_tilt_since_seconds:-1
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=172.31.32.27:6379,slaves=2,sentinels=3
root@ArunValkeyPrimary:/home/ubuntu#</pre>
<p><b>Wrapping Up</b></p>
<p><span style="font-weight: 400">By combining replication with Sentinel, a single cache becomes a highly available, self-healing data cluster. If hardware fails or network hiccups occur, Sentinel automatically handles the reshuffling. Furthermore, as demonstrated, system administrators still retain full control to manually shuffle roles during planned maintenance or load balancing.</span></p>
<p>The post <a href="https://www.percona.com/blog/achieving-high-availability-with-valkey-sentinel/">Achieving High Availability with Valkey Sentinel</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/achieving-high-availability-with-valkey-sentinel/">Achieving High Availability with Valkey Sentinel</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Innovation From Every Corner: Inside Percona’s Build with AI Competition</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/innovation-from-every-corner-inside-perconas-build-with-ai-competition/" />
      <id>https://www.percona.com/blog/innovation-from-every-corner-inside-perconas-build-with-ai-competition/</id>
      <updated>2026-04-23T18:53:00+00:00</updated>
      <author><name>Bennie Grant</name></author>
      <summary type="html"><![CDATA[<p>At Percona, we’re passionate about open source database software, helping organizations of all sizes run, manage, and optimize their databases with the freedom and transparency that open source provides. That spirit of openness doesn’t stop at our products, it runs through everything we do, including how we encourage our own people to innovate. We recently … Continued<br />
The post Innovation From Every Corner: Inside Percona’s Build with AI Competition appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/innovation-from-every-corner-inside-perconas-build-with-ai-competition/">Innovation From Every Corner: Inside Percona’s Build with AI Competition</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">At Percona, we&rsquo;re passionate about open source database software, helping organizations of all sizes run, manage, and optimize their databases with the freedom and transparency that open source provides. That spirit of openness doesn&rsquo;t stop at our products, it runs through everything we do, including how we encourage our own people to innovate.</span></p>
<p><span style="font-weight: 400">We recently ran a 6-week &ldquo;Build with AI&rdquo; competition here at Percona, where we invited all Perconians to use their AI technology/tool of choice to solve a problem, create something new, or improve a product or internal process that they felt was worth improving.&nbsp;</span></p>
<p><span style="font-weight: 400">In the spirit of our belief that </span><b>The Way Is Open</b><span style="font-weight: 400">, that open source should mean real freedom, not lock-in, inflated costs, or hollow promises, we encouraged as much transparency as possible sharing ideas with colleagues, and where appropriate with the community as well. &ldquo;</span><b><i>Default to building in open</i></b><span style="font-weight: 400">&rdquo; was the guideline.</span></p>
<p>&nbsp;</p>
<h2><b>The Rules</b><a class="anchor-link" id="the-rules"></a></h2>
<p><span style="font-weight: 400">The rules were simple and minimal.</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Do whatever you like. Whether that&rsquo;s building new products or features that our customers and community will value, creating internal tools that help us work smarter and faster, improving our customer experience, or even tearing down processes and workflows that have outlived their usefulness.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Use whichever tool you want &ndash; our Percona-approved AI toolset, or other tools we haven&rsquo;t bought en masse (yet). Expense your token spending if needed &ndash; we&rsquo;ll cover the cost.&nbsp;</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Submit as many times as you like &ndash; multiple projects are welcome</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Keep our compliance &amp; governance policies in mind around customer/confidential data, we want to build responsibly.</span></li>
</ol>
<p>&nbsp;</p>
<p><span style="font-weight: 400">The Top 3 (as judged by our competition judges, Peter Farkas (CEO), Peter Zaitsev (Founder), Vadim Tkachenko (Co-Founder), and me (COO) would win prizes.&nbsp;</span></p>
<p><span style="font-weight: 400">It&rsquo;s easy to forget the breadth of talent and innovative ideas we have at Percona. I am fiercely proud to say that we&rsquo;re surrounded by the best of the best every day, and I say it often to customers, partners, and colleagues. This competition was a great reminder! </span><span style="font-weight: 400">We had over 40 submissions from across the company, and from Perconians in 10+ teams. We saw representation from our Customer Success, Support, Engineering, Marketing, Product Management, Community, Legal and Contracts team, Professional and Managed Services teams, and more.&nbsp;</span></p>
<p><span style="font-weight: 400">We ran into two stereotypical &ldquo;great problems to have&rdquo;:</span></p>
<p><span style="font-weight: 400">First,&nbsp; we scheduled an entire day for the demos and presentations, and it wasn&rsquo;t enough time. Fortunately, we found extra time pretty quickly.&nbsp;</span></p>
<p><span style="font-weight: 400">Second &ndash; it was simply impossible to pick only 3 winners! There were just too many great ideas, so we had to get creative&hellip;</span></p>
<p>&nbsp;</p>
<h2><b>The Winners</b><a class="anchor-link" id="the-winners"></a></h2>
<p><span style="font-weight: 400">After a tough deliberation, here&rsquo;s how our judges landed:</span></p>
<p><span style="font-weight: 400">With no further ado, the first-place winner was Tibi Korocz (Observability Tech Lead). Tibi extended PMM to become an </span><b>AI-assisted incident workspace</b><span style="font-weight: 400">, helping users not only understand their database environment better but also receive intelligent insights and real recommendations to improve, optimize, and fix issues. It&rsquo;s also integrated with Percona Services, improving customer experience through integration with our ServiceNow platform. It&rsquo;s a deserving winner and is worthy of its own blog post, which Tibi will publish (this article will be updated with the link)</span></p>
<p><span style="font-weight: 400">Second was Dennis Kittrell (head of MySQL product &amp; engineering). Dennis built a suite of open-source tools &mdash; including connectors for an internally hosted AI assistant that integrate Slack, Notion, Jira, and ServiceNow, and a local semantic search engine for Percona documentation &mdash; that together give Perconians smarter, faster access to the knowledge they need every day.</span></p>
<p><span style="font-weight: 400">Third place was Agustin Gallego (Lead Database Performance Engineer), who built a Postgres extension that produces pt-query-digest-compatible slow query logs with extended PostgreSQL-specific metrics, inspired by Agustin&rsquo;s vast MySQL experience and his belief that &ldquo;</span><i><span style="font-weight: 400">thinking about Percona Server&rsquo;s extended slow logging capabilities and pt-query-digest, I&rsquo;ve always felt we could do better in Postgres.&rdquo;</span></i></p>
<p>&nbsp;</p>
<h2><b>Special Recognition Awards</b><a class="anchor-link" id="special-recognition-awards"></a></h2>
<p><span style="font-weight: 400">We also recognized the following:</span></p>
<p><strong>Community Impact Award</strong><span style="font-weight: 400"> to Zsolt Parragi and Kai Wagner (Postgres Engineering team), for their </span><a href="https://hackorum.dev"><span style="font-weight: 400">hackorum.dev</span></a><span style="font-weight: 400"> site. A new frontend for the pgsql hackers mailing list, something that has been missing in the community for a long time, and is already live. Check it out!</span></p>
<p><strong>AI Transformation Award</strong><span style="font-weight: 400"> to Scott LaFortune and Kim Reddy (Marketing team), who built an internal platform for managing and creating materials (content, copy, campaigns, etc) that specifically encodes Percona&rsquo;s brand voice and compliance rules so any Percona marketer gets on-brand output without needing to know how to prompt Claude, and kick-starts the AI transformation of our marketing team.</span></p>
<p><strong>The Dare to Try</strong><span style="font-weight: 400"> award went to Molly Fulton in our contracts and legal team, who wanted to &ldquo;</span><i><span style="font-weight: 400">pressure test the idea that &ldquo;anyone can enter&rdquo; and represent non-technical Perconians in this challenge</span></i><span style="font-weight: 400">&rdquo;, and she did it by building a tool to help Perconians learn more about how our contracts team identifies and mitigates risk for our customers!</span></p>
<p><span style="font-weight: 400">Congratulations to all of our winners! And also to all of our submissions &ndash; it was difficult to pick the winners, and we have decided that we are going to run this competition again later in the year. It was simply too good to be a one-time thing. A lot of the projects are going to continue and likely turn into full initiatives &ndash; I expect we will have more blog posts about them in the coming weeks and months.</span></p>
<p><span style="font-weight: 400">Of course, I couldn&rsquo;t finish this post without making a small request. If this is the sort of thing that excites you and you want to be at a company that encourages, embraces, and rewards this sort of innovation and experimentation, check out our </span><a href="https://www.percona.com/careers/"><span style="font-weight: 400">careers page</span></a><span style="font-weight: 400"> and join us!&nbsp;</span></p>
<p>The post <a href="https://www.percona.com/blog/innovation-from-every-corner-inside-perconas-build-with-ai-competition/">Innovation From Every Corner: Inside Percona&rsquo;s Build with AI Competition</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/innovation-from-every-corner-inside-perconas-build-with-ai-competition/">Innovation From Every Corner: Inside Percona’s Build with AI Competition</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>A Clearer Path Forward for GridGain Customers</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/a-clearer-path-forward-for-gridgain-customers/" />
      <id>https://mariadb.com/resources/blog/a-clearer-path-forward-for-gridgain-customers/</id>
      <updated>2026-04-23T17:12:13+00:00</updated>
      <author><name>Vikas Mathur</name></author>
      <summary type="html"><![CDATA[<p>This blog was originally published on GridGain’s website. When MariaDB evaluated and acquired GridGain, we noticed something important: GridGain is […]</p>
<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/a-clearer-path-forward-for-gridgain-customers/">A Clearer Path Forward for GridGain Customers</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p>This blog was originally published on GridGain&rsquo;s website. When MariaDB evaluated and acquired GridGain, we noticed something important: GridGain is not a platform used on the margins of an enterprise, instead it sits at the heart of systems that matter deeply to the business. Customers rely on it for payments, fraud prevention, risk management, customer 360, and other applications where ultra&hellip;</p>
<p><a href="https://mariadb.com/resources/blog/a-clearer-path-forward-for-gridgain-customers/" rel="nofollow">Source</a></p>

<p>The post <a rel="nofollow" href="https://mariadb.com/resources/blog/a-clearer-path-forward-for-gridgain-customers/">A Clearer Path Forward for GridGain Customers</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
      <entry>
      <title>Scaling Your Cache: A Step-by-Step Guide to Setting Up Valkey Replication</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/scaling-your-cache-a-step-by-step-guide-to-setting-up-valkey-replication/" />
      <id>https://www.percona.com/blog/scaling-your-cache-a-step-by-step-guide-to-setting-up-valkey-replication/</id>
      <updated>2026-04-23T16:46:27+00:00</updated>
      <author><name>Arunjith Aravindan</name></author>
      <summary type="html"><![CDATA[<p>In the recent open-source data landscape, Valkey has emerged as a prominent player. Born as a Linux Foundation-backed, fully open-source fork of Redis (following Redis’s recent licensing changes), Valkey serves as a high-performance, in-memory key-value data store. Whether Valkey is deployed as a primary database, an ephemeral cache, or a rapid message broker, a single … Continued<br />
The post Scaling Your Cache: A Step-by-Step Guide to Setting Up Valkey Replication appeared first on Percona.</p>
<p>The post <a rel="nofollow" href="https://www.percona.com/blog/scaling-your-cache-a-step-by-step-guide-to-setting-up-valkey-replication/">Scaling Your Cache: A Step-by-Step Guide to Setting Up Valkey Replication</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">In the recent open-source data landscape, Valkey has emerged as a prominent player. Born as a Linux Foundation-backed, fully open-source fork of Redis (following Redis&rsquo;s recent licensing changes), Valkey serves as a high-performance, in-memory key-value data store.</span></p>
<p><span style="font-weight: 400">Whether Valkey is deployed as a primary database, an ephemeral cache, or a rapid message broker, a single node is rarely sufficient for production workloads, as it creates a single point of failure. Ensuring high availability and scaling out read operations requires replication.</span></p>
<p><span style="font-weight: 400">This comprehensive guide explores how to configure a Primary-Replica (Master-Slave) replication topology in Valkey, detailing its underlying mechanics and the verification process.</span></p>
<h2><b>How Valkey Replication Works</b><a class="anchor-link" id="how-valkey-replication-works"></a></h2>
<p><span style="font-weight: 400">Valkey&rsquo;s replication is asynchronous and non-blocking. When a replica connects to a primary node, it initiates a synchronisation process.</span></p>
<p><span style="font-weight: 400">Initially, the primary creates a snapshot of its entire dataset in memory (an RDB file) and sends it to the replica. Once this initial full sync is complete, the primary continuously streams a log of all new write operations to the replica. Because this happens asynchronously, the primary node does not wait for the replica to acknowledge writes, meaning applications experience zero latency penalty from the replication process.</span></p>
<h2><b>Why Use Replication?</b><a class="anchor-link" id="why-use-replication"></a></h2>
<p><span style="font-weight: 400">Before diving into the configuration commands, it is important to understand the concrete benefits of this architecture:</span></p>
<ul>
<li style="font-weight: 400"><b>Data Redundancy: </b><span style="font-weight: 400">Replication maintains a near real-time copy of the data on replicas. However, because replication is asynchronous, there may be a small delay, and recent writes might not be fully replicated at the moment of a primary failure. For applications requiring stronger durability guarantees, the </span><span style="font-weight: 400">WAIT</span><span style="font-weight: 400"> command can be used to ensure that writes are acknowledged by one or more replicas.</span></li>
<li style="font-weight: 400"><b>Read Scaling:</b><span style="font-weight: 400"> Heavy read operations (like </span><span style="font-weight: 400">GET</span><span style="font-weight: 400"> or </span><span style="font-weight: 400">LRANGE</span><span style="font-weight: 400"> commands) can be offloaded to replicas. This frees up the primary node to dedicate its CPU and network bandwidth to handling write operations efficiently.</span></li>
<li style="font-weight: 400"><b>High Availability:</b><span style="font-weight: 400"> When paired with Valkey Sentinel or a cluster manager, replication forms the foundational layer for automatic failover.</span></li>
</ul>
<h2><b>Prerequisites</b><a class="anchor-link" id="prerequisites"></a></h2>
<p><span style="font-weight: 400">The following components are required:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Two servers, virtual machines, or containers with Valkey installed.</span></li>
<li style="font-weight: 400"><b>Network connectivity:</b><span style="font-weight: 400"> The replica must be able to reach the primary on its Valkey port. Ensure firewalls (UFW, iptables) or cloud security groups (AWS, GCP) allow TCP traffic on port 6379 between the specified IPs.</span></li>
</ul>
<p><span style="font-weight: 400">This tutorial uses the following hypothetical IP addresses:</span></p>
<ul>
<li style="font-weight: 400"><b>Primary Node:</b><span style="font-weight: 400"> 172.31.32.27</span></li>
<li style="font-weight: 400"><b>Replica Node:</b><span style="font-weight: 400"> 172.31.37.55</span></li>
<li style="font-weight: 400"><b>Valkey Port:</b><span style="font-weight: 400"> 6379 (the default)</span></li>
</ul>
<h2><b>Step 1: Configure the Primary Node</b><a class="anchor-link" id="step-1-configure-the-primary-node"></a></h2>
<p><span style="font-weight: 400">By default, Valkey binds only to localhost (127.0.0.1), meaning it rejects connections from outside servers. This must be adjusted to allow replica connections.</span></p>
<ol>
<li><span style="font-weight: 400"> Open the Valkey configuration file on the Primary Node (typically located at </span><span style="font-weight: 400">/etc/valkey/valkey.conf</span><span style="font-weight: 400">, depending on the installation method).</span></li>
</ol>
<p><span style="font-weight: 400">&nbsp; &nbsp; &nbsp; 2. Find the </span><span style="font-weight: 400">bind</span><span style="font-weight: 400"> directive. Update it to listen on both localhost and the internal network IP. Note: While </span><span style="font-weight: 400">*</span><span style="font-weight: 400"> can be used to listen on all interfaces, specifying the exact IP provides better security.</span></p>
<p><span style="font-weight: 400">bind 127.0.0.1 </span><span style="font-weight: 400">172.31.32.27</span></p>
<ol start="3">
<li>Configure ACLs (Access Control Lists) for Security and Replication:</li>
</ol>
<p><span style="font-weight: 400">&nbsp;While older versions of Redis used the </span><span style="font-weight: 400">requirepass</span><span style="font-weight: 400"> directive, Valkey utilizes modern ACLs. Securing the default user and creating a dedicated, restricted user specifically for replication is highly recommended. This ensures the replica only has the permissions necessary to sync data. Add the following ACL rules:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag"># Secure the default user so unauthorized users cannot access the database
user default on &gt;amma@123 ~* &amp;* +@all

# Create a dedicated replication user with ONLY the permissions needed to sync
user repl_user on &gt;ReplPass@123 -@all +psync +replconf +ping +info</pre>

<ol start="4">
<li><span style="font-weight: 400"> Restart the Valkey service to apply the changes:</span></li>
</ol>
<pre class="urvanov-syntax-highlighter-plain-tag">sudo systemctl restart valkey</pre>

<h2><b>Step 2: Configure the Replica Node</b><a class="anchor-link" id="step-2-configure-the-replica-node"></a></h2>
<p><span style="font-weight: 400">Now, let&rsquo;s move over to the Replica Node (</span><span style="font-weight: 400">172.31.37.55</span><span style="font-weight: 400">). There are two ways to configure a replica: dynamically via the CLI using the </span><span style="font-weight: 400">REPLICAOF</span><span style="font-weight: 400"> command (which resets upon reboot) or permanently via the configuration file. For production, we will use the permanent method.</span></p>
<ol>
<li><span style="font-weight: 400"> Open the Valkey configuration file on the Replica.</span></li>
<li><span style="font-weight: 400"> Locate the </span><span style="font-weight: 400">replicaof</span><span style="font-weight: 400"> directive (in older versions or Redis compat mode, this might be </span><span style="font-weight: 400">slaveof</span><span style="font-weight: 400">). Uncomment it and add the Primary&rsquo;s IP and port:</span></li>
</ol>
<pre class="urvanov-syntax-highlighter-plain-tag">replicaof 172.31.32.27 6379</pre>

<ol start="3">
<li><b> Authenticate using the Replication ACL:</b><span style="font-weight: 400"> Because a dedicated </span><span style="font-weight: 400">repl_user</span><span style="font-weight: 400"> was created on the primary node, the replica must be configured to use those specific credentials. Find the </span><span style="font-weight: 400">masteruser</span><span style="font-weight: 400"> and </span><span style="font-weight: 400">masterauth</span><span style="font-weight: 400"> directives and update them:</span></li>
</ol>
<pre class="urvanov-syntax-highlighter-plain-tag">masteruser repl_user
masterauth ReplPass@123</pre>

<ol start="4">
<li><i><span style="font-weight: 400">(Optional but recommended)</span></i><span style="font-weight: 400"> Ensure the replica is strictly read-only by checking this directive:</span></li>
</ol>
<pre class="urvanov-syntax-highlighter-plain-tag">replica-read-only yes</pre>
<p><span style="font-weight: 400">Restart the Valkey service on the replica:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">sudo systemctl restart valkey</pre>

<h2><b>Step 3: Verify the Replication</b><a class="anchor-link" id="step-3-verify-the-replication"></a></h2>
<p><span style="font-weight: 400">The replica should now be connecting to the primary and synchronizing the dataset. Let&rsquo;s verify the connection and test the data flow.</span></p>
<h3><b>Checking Replication Status</b><a class="anchor-link" id="checking-replication-status"></a></h3>
<p><span style="font-weight: 400">Log in to the </span><b>Primary Node</b><span style="font-weight: 400"> using the Valkey CLI.</span></p>
<p><b>Pro tip:</b><span style="font-weight: 400"> The CLI generates a warning when passing a password with </span><span style="font-weight: 400">-a</span><span style="font-weight: 400">, as it appears in bash history. For production systems, consider utilizing the </span><span style="font-weight: 400">VALKEYCLI_AUTH</span><span style="font-weight: 400"> environment variable instead. </span><i><span style="font-weight: 400">Note for Valkey 7.2.x users: As the first release following the Redis fork, the system still uses the legacy </span></i><i><span style="font-weight: 400">REDISCLI_AUTH</span></i><i><span style="font-weight: 400"> environment variable name.</span></i></p>
<pre class="urvanov-syntax-highlighter-plain-tag">valkey-cli -a YourSuperSecurePassword</pre>
<p><span style="font-weight: 400">Once inside the prompt, type the </span><span style="font-weight: 400">INFO replication</span><span style="font-weight: 400"> command:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">root@ArunValkeyPrimary:/home/ubuntu# valkey-cli -a&nbsp; amma@123

127.0.0.1:6379&gt; INFO replication
# Replication
role:master
connected_slaves:1
slave0:ip=172.31.37.55,port=6379,state=online,offset=126,lag=1
master_failover_state:no-failover
master_replid:7ef2ac2cc03b211c9571da0c0a53899327177349
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:126
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:126
127.0.0.1:6379&gt;</pre>
<p>Warning: Using a password with &lsquo;-a&rsquo; or &lsquo;-u&rsquo; option on the command line interface may not be safe.</p>
<p><b>Key Metrics to Watch:</b></p>
<ul>
<li><b>role</b><b>: Confirms this node is the master/primary.</b></li>
</ul>
<ul>
<li><b>state=online</b><b>: The replica is fully synced and streaming.</b></li>
</ul>
<ul>
<li><b>lag=0</b><b>: The replica is up to date. If this number climbs, the replica is struggling to keep up with the primary&rsquo;s write volume.</b></li>
</ul>
<ul>
<li><b>offset</b><b>: Matches the </b><b>master_repl_offset</b><b>, confirming data parity</b></li>
</ul>
<h3><b>The Write Test</b><a class="anchor-link" id="the-write-test"></a></h3>
<p><span style="font-weight: 400">To double-check the data flow, set a key on the primary:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag"># On Primary (172.31.32.27)
127.0.0.1:6379&gt; SET blog_status "Replication works!"
OK
127.0.0.1:6379&gt;</pre>
<p><span style="font-weight: 400">Then, hop over to the Replica&rsquo;s CLI and read the key:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag"># On Replica (172.31.37.55)
127.0.0.1:6379&gt; GET blog_status
"Replication works!"
127.0.0.1:6379&gt;</pre>

<h2><b>Wrapping Up</b><a class="anchor-link" id="wrapping-up"></a></h2>
<p><span style="font-weight: 400">With just a few configuration changes, you&rsquo;ve transformed a single-node Valkey setup into a scalable, production-ready replication topology. You now have data redundancy, improved read performance, and a solid foundation for growth.</span></p>
<p><span style="font-weight: 400">But there&rsquo;s one missing piece&mdash;automatic failover. If the primary node goes down, the replicas won&rsquo;t take over automatically. That&rsquo;s where the next evolution comes in.</span></p>
<p><span style="font-weight: 400">In the upcoming </span><a href="https://docs.google.com/document/d/1F_HtsNKi_-uf2Sma0WvHhFEreUlcIsJbC3ZRKeyOEEU/edit?usp=sharing"><span style="font-weight: 400">guide</span></a><span style="font-weight: 400">, (<b>Achieving High Availability with Valkey Sentinel) </b>I will dive into Valkey Sentinel and show how to turn this replicated setup into a fully self-healing, highly available system.</span></p>
<p>The post <a href="https://www.percona.com/blog/scaling-your-cache-a-step-by-step-guide-to-setting-up-valkey-replication/">Scaling Your Cache: A Step-by-Step Guide to Setting Up Valkey Replication</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p>The post <a rel="nofollow" href="https://www.percona.com/blog/scaling-your-cache-a-step-by-step-guide-to-setting-up-valkey-replication/">Scaling Your Cache: A Step-by-Step Guide to Setting Up Valkey Replication</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>
]]></content>
    </entry>
    </feed>