<?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-08-16T16:00:46+03:00</updated>
  <id>https://mariadb.org/planet-atom</id>

        <entry>
      <title>Extending MariaDB with Native Aggregate Plugins: Laying the Groundwork for HyperLogLog</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/extending-mariadb-with-native-aggregate-plugins-laying-the-groundwork-for-hyperloglog/" />
      <id>https://mariadb.org/extending-mariadb-with-native-aggregate-plugins-laying-the-groundwork-for-hyperloglog/</id>
      <updated>2026-08-16T16:00:46+00:00</updated>
      <author><name>Roman Nozdrin</name></author>
      <summary type="html"><![CDATA[<p>MariaDB already allows developers to add new Pluggable Data Types and scalar Plugin Functions. One missing piece has been Pluggable Aggregate Functions operating on PDTs. That matters for functionality such as HyperLogLog, where an extension needs to aggregate values into a custom statistical sketch while preserving its native SQL type. …<br />
Continue reading \"Extending MariaDB with Native Aggregate Plugins: Laying the Groundwork for HyperLogLog\"<br />
Extending MariaDB with Native Aggregate Plugins: Laying the Groundwork for HyperLogLog appeared first on MariaDB.org</p>
<p><a href="https://mariadb.org/extending-mariadb-with-native-aggregate-plugins-laying-the-groundwork-for-hyperloglog/">Extending MariaDB with Native Aggregate Plugins: Laying the Groundwork for HyperLogLog</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB already allows developers to add new Pluggable Data Types and scalar Plugin Functions. One missing piece has been Pluggable Aggregate Functions operating on PDTs. That matters for functionality such as HyperLogLog, where an extension needs to aggregate values into a custom statistical sketch while preserving its native SQL type. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/extending-mariadb-with-native-aggregate-plugins-laying-the-groundwork-for-hyperloglog/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Extending MariaDB with Native Aggregate Plugins: Laying the Groundwork for HyperLogLog&rdquo;</span></a></p>
<p><a href="https://mariadb.org/extending-mariadb-with-native-aggregate-plugins-laying-the-groundwork-for-hyperloglog/">Extending MariaDB with Native Aggregate Plugins: Laying the Groundwork for HyperLogLog</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>

<p><a href="https://mariadb.org/extending-mariadb-with-native-aggregate-plugins-laying-the-groundwork-for-hyperloglog/">Extending MariaDB with Native Aggregate Plugins: Laying the Groundwork for HyperLogLog</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Foundation Advances TAF with HammerDB 6.0 and xt_reservoir Integration</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-foundation-advances-taf-with-hammerdb-6-0-and-xt_reservoir-integration/" />
      <id>https://mariadb.org/mariadb-foundation-advances-taf-with-hammerdb-6-0-and-xt_reservoir-integration/</id>
      <updated>2026-08-14T15:48:21+00:00</updated>
      <author><name>Jonathan Miller</name></author>
      <summary type="html"><![CDATA[<p>Overview<br />
While validating MariaDB RSS stability under stored procedure workloads, I ran into unexpected memory growth. The goal was straightforward: confirm that MariaDB was not leaking memory when running TPROC-C stored procedure workloads. …<br />
Continue reading \"MariaDB Foundation Advances TAF with HammerDB 6.0 and xt_reservoir Integration\"<br />
MariaDB Foundation Advances TAF with HammerDB 6.0 and xt_reservoir Integration appeared first on MariaDB.org</p>
<p><a href="https://mariadb.org/mariadb-foundation-advances-taf-with-hammerdb-6-0-and-xt_reservoir-integration/">MariaDB Foundation Advances TAF with HammerDB 6.0 and xt_reservoir Integration</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Overview<br>
While validating MariaDB RSS stability under stored procedure workloads, I ran into unexpected memory growth. The goal was straightforward: confirm that MariaDB was not leaking memory when running TPROC-C stored procedure workloads. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-foundation-advances-taf-with-hammerdb-6-0-and-xt_reservoir-integration/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Foundation Advances TAF with HammerDB 6.0 and xt_reservoir Integration&rdquo;</span></a></p>
<p><a href="https://mariadb.org/mariadb-foundation-advances-taf-with-hammerdb-6-0-and-xt_reservoir-integration/">MariaDB Foundation Advances TAF with HammerDB 6.0 and xt_reservoir Integration</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>

<p><a href="https://mariadb.org/mariadb-foundation-advances-taf-with-hammerdb-6-0-and-xt_reservoir-integration/">MariaDB Foundation Advances TAF with HammerDB 6.0 and xt_reservoir Integration</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Community Server 10.6.28 now available</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/mariadb-community-server-10-6-28-now-available/" />
      <id>https://mariadb.com/resources/blog/mariadb-community-server-10-6-28-now-available/</id>
      <updated>2026-08-13T20:30:08+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 Community Server 10.6.28 maintenance release. This is the final […]</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-community-server-10-6-28-now-available/">MariaDB Community Server 10.6.28 now available</a> appeared first on <a 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 Community Server 10.6.28 maintenance release. This is the final release in the MariaDB 10.6 series. See the release notes and changelog for additional details on this release and visit mariadb.com/downloads to download.</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-community-server-10-6-28-now-available/" rel="nofollow">Source</a></p>

<p><a href="https://mariadb.com/resources/blog/mariadb-community-server-10-6-28-now-available/">MariaDB Community Server 10.6.28 now available</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Replicating from InnoDB into a DuckDB storage engine</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/replicating-from-innodb-into-a-duckdb-storage-engine/" />
      <id>https://www.percona.com/blog/replicating-from-innodb-into-a-duckdb-storage-engine/</id>
      <updated>2026-08-13T17:23:27+00:00</updated>
      <author><name>Evgeniy Patlan</name></author>
      <summary type="html"><![CDATA[<p>Our first post showed MySQL 9.7 with one change: mark a table ENGINE=DuckDB and its analytical queries run in DuckDB instead of InnoDB. The question we kept getting after that was about replication. Can you keep a normal InnoDB primary for the writes, and run a replica where the big tables are ENGINE=DuckDB? Then the … Continued<br />
The post Replicating from InnoDB into a DuckDB storage engine appeared first on Percona.</p>
<p><a href="https://www.percona.com/blog/replicating-from-innodb-into-a-duckdb-storage-engine/">Replicating from InnoDB into a DuckDB storage engine</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">Our first post showed MySQL 9.7 with one change: mark a table ENGINE=DuckDB and its analytical queries run in DuckDB instead of InnoDB. The question we kept getting after that was about replication. Can you keep a normal InnoDB primary for the writes, and run a replica where the big tables are ENGINE=DuckDB? Then the heavy reports run on a column store, and ordinary MySQL replication keeps it current. No export job. No second database to sync by hand.</span></p>
<p><span style="font-weight: 400">So we tried it. The first run failed, and it failed in a way that is easy to miss: the replica took every transaction, reported success, and stored nothing. We tracked down why, fixed it, and the whole test suite passes now. This post is what we tested, how we checked it, the bug we found, and where it stands.</span></p>
<p><span style="font-weight: 400">It&rsquo;s still an experiment, not production software. The code and the test harness are on GitHub under GPLv2: </span><a href="https://github.com/Percona-Lab/ducksdb-mysql-engine"><span style="font-weight: 400">https://github.com/Percona-Lab/ducksdb-mysql-engine</span></a><span style="font-weight: 400">.</span></p>
<h2><span style="font-weight: 400">Why replicate into DuckDB</span><a class="anchor-link" id="why-replicate-into-duckdb"></a></h2>
<p><span style="font-weight: 400">A DuckDB table on one server is already useful. The analytical queries get fast and the application does not change. But almost nobody runs their reports on the primary &ndash; they run them on a replica, so the big scans stay out of the way of the OLTP traffic.</span></p>
<p><span style="font-weight: 400">So the shape of it is simple. The primary stays InnoDB and takes the writes. The replica has the same tables, only marked ENGINE=DuckDB. Row-based replication ships the changes across, the replica writes them into the column store, and the reports run there. You get an analytics replica out of the replication you already run.</span></p>
<p><span style="font-weight: 400">Row events are engine-agnostic on purpose. The primary logs the row changes, not the SQL, and the replica applies them through the storage-engine API. On paper, then, the replica should not care that one side is InnoDB and the other DuckDB. We wanted to see the paper version hold up on a running server.</span></p>
<h2><span style="font-weight: 400">The setup</span><a class="anchor-link" id="the-setup"></a></h2>
<p><span style="font-weight: 400">Two containers from the same image, one primary and one replica. It&rsquo;s all in Docker, so it repeats cleanly.</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Primary: InnoDB, binlog_format=ROW, GTID on.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Replica: same server, GTID on, tables made with ENGINE=DuckDB.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Replication uses SOURCE_AUTO_POSITION=1.</span></li>
</ul>
<p><span style="font-weight: 400">One thing you have to get right before any data moves. Create the replica tables as ENGINE=DuckDB yourself. A CREATE TABLE &hellip; ENGINE=InnoDB on the primary goes into the binlog with the ENGINE word still in it, and the replica runs it exactly as written, so you would end up with an InnoDB table there, not a DuckDB one. There is no automatic mapping. Pre-create the DuckDB tables on the replica, and let the row changes flow into them.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">-- primary (InnoDB)
CREATE TABLE t1 (id BIGINT PRIMARY KEY, region INT, amount DECIMAL(12,2)) ENGINE=InnoDB;

-- replica (same columns, DuckDB)
CREATE TABLE t1 (id BIGINT PRIMARY KEY, region INT, amount DECIMAL(12,2)) ENGINE=DuckDB;</pre>
<p><span style="font-weight: 400">The other rule is a primary key on the replica table. UPDATE and DELETE row events find the row by its old image, and the engine needs the key for that. INSERT works without one, but put a key on it anyway.</span></p>
<p><span style="font-weight: 400">One script drives all of this: bench/tb/07-replication-spike.sh. It starts both containers, wires up replication, runs every scenario below, and prints PASS or FAIL for each.</span></p>
<h2><span style="font-weight: 400">What we tested, and how</span><a class="anchor-link" id="what-we-tested-and-how"></a></h2>
<p><span style="font-weight: 400">The part that matters is the checking. Row counts are not enough &ndash; the replica can hold the right number of rows and still have the wrong data in them. So after each step the script dumps the whole table on both sides, ordered by primary key, and compares an md5 of the two dumps. One byte off is a FAIL. And rather than sleep between steps, it waits on WAIT_FOR_EXECUTED_GTID_SET(), so the checks do not race the replica.</span></p>
<p><span style="font-weight: 400">Here is what went through it.</span></p>
<p><span style="font-weight: 400">Basic DML. Insert, update a row, delete a row, compared after each one.</span></p>
<p><span style="font-weight: 400">All the column types, in a single wide table: signed and unsigned integers, DECIMAL, DOUBLE, DATE, DATETIME, TIMESTAMP, CHAR, VARCHAR, TEXT, BLOB, a few NULLs, and a unicode string. Insert it, update it, compare byte for byte. Blobs get their own note below.</span></p>
<p><span style="font-weight: 400">DDL. ALTER TABLE ADD COLUMN, ALTER TABLE ADD INDEX, and DROP TABLE against a DuckDB replica table. These arrive as statements. We check that the column shows up, the index shows up, the old rows survive, and the drop removes the table.</span></p>
<p><span style="font-weight: 400">Transactions. A transaction with two inserts and an update has to land on the replica as one unit. A transaction the primary rolls back has to leave nothing behind. We also open a transaction straight on the replica and both roll it back and commit it, to check the engine&rsquo;s own commit and rollback.</span></p>
<p><span style="font-weight: 400">Bulk load. 5000 rows through LOAD DATA on the primary, has to arrive and match.</span></p>
<p><span style="font-weight: 400">Durability. Two cases, and the second is the hard one.</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Clean restart. Stop the replica properly, write on the primary while it is down, start it again, and see it pick up from its GTID position.</span></li>
</ul>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Crash. Apply some rows, then SIGKILL the replica. No clean shutdown, no checkpoint. Bring it back, write more on the primary, and check one exact thing: every row present once. Nothing lost &ndash; DuckDB has to replay its write-ahead log when it opens the file &ndash; and nothing applied twice, which means the saved position has to line up with the data that actually reached disk.</span></li>
</ul>
<h2><span style="font-weight: 400">The bug: multi-engine transactions lost data</span><a class="anchor-link" id="the-bug-multi-engine-transactions-lost-data"></a></h2>
<p><span style="font-weight: 400">The first full run fell down on the wide-table test. Zero rows on the replica, and then everything after it failed too. The applier had stopped with HA_ERR_KEY_NOT_FOUND. It went to UPDATE a row that was not there, because the INSERT before it had returned success and written nothing.</span></p>
<p><span style="font-weight: 400">When a scenario fails, the harness saves the applier error, both server logs, and both schemas. The replica log had the line that mattered:</span></p>
<p><span style="font-weight: 400">[Warning] Combining the storage engines InnoDB and DuckDB is deprecated, but the</span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400">statement or transaction updates both the InnoDB table mysql.slave_worker_info and the</span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400">DuckDB table rpl.wide.</span></p>
<p><span style="font-weight: 400">That line is the whole thing. A replica does not only write your data. In the same transaction it also writes its own position into InnoDB system tables &ndash; mysql.slave_worker_info, the relay-log info, gtid_executed. So every applied transaction touches two engines at once: InnoDB for the position, DuckDB for the data. Two engines means MySQL runs a real two-phase commit: prepare, then commit.</span></p>
<p><span style="font-weight: 400">Our prepare was wrong. It took the open DuckDB transaction, moved it into a registry meant for external XA COMMIT, and cleared the per-connection state. Then commit looked at that state, found it empty, and committed nothing. The position went into InnoDB, the GTID advanced, the binlog moved on, and the DuckDB rows were thrown away. No error anywhere. The replica looked healthy while it dropped every write.</span></p>
<p><span style="font-weight: 400">We cut it down to the smallest case, with no replication at all. One server, one transaction into a DuckDB table and an InnoDB table:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">BEGIN;
INSERT INTO duck VALUES (1,10),(2,20),(3,30); &nbsp; -- DuckDB
INSERT INTO inno VALUES (1,10),(2,20),(3,30); &nbsp; -- InnoDB
COMMIT;
-- duck: 0 rows &nbsp; inno: 3 rows</pre>
<p><span style="font-weight: 400">InnoDB kept its three rows, DuckDB kept none, and COMMIT said it was fine. A DuckDB-only transaction was fine as well, because with one engine MySQL skips the prepare step. It only broke with a second engine in the transaction. And on a replica, that is every transaction.</span></p>
<h2><span style="font-weight: 400">The fix</span><a class="anchor-link" id="the-fix"></a></h2>
<p><span style="font-weight: 400">Small change, in the engine&rsquo;s transaction code. prepare now remembers which prepared transaction belongs to the connection, and commit finishes that one instead of an empty state. External XA is untouched. It went out as v0.2.3.</span></p>
<p><span style="font-weight: 400">With that in place the reproducer keeps three rows in both tables, and the full run comes back clean, crash test included:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">[8]&nbsp; data integrity: all column types, NULL / unicode / negatives ....... PASS
[9]&nbsp; DDL replication (ALTER ADD COLUMN / ADD INDEX / DROP) .............. PASS
[10] transactions (atomic commit, rollback, engine commit/rollback) ..... PASS
[11] bulk LOAD DATA on master -&gt; replica ................................ PASS
[12] durability: graceful restart, then SIGKILL crash recovery .......... PASS

VERDICT: PASS=24&nbsp; FAIL=0</pre>
<p><span style="font-weight: 400">The crash case is the important one. After a SIGKILL in the middle of applying, the replica came back with every committed row exactly once, matching the primary. Committed transactions survive the kill, and the position stays in step with them.</span></p>
<p><span style="font-weight: 400">We left two tests behind so this cannot slip back in quietly: an MTR test, txn_mixed_engine, that runs a mixed DuckDB+InnoDB transaction on every build, and scripts/repro-2pc-dataloss.sh, which you can point at any published image to check it.</span></p>
<h2><span style="font-weight: 400">What works, and what doesn&rsquo;t yet</span><a class="anchor-link" id="what-works-and-what-doesnt-yet"></a></h2>
<p><span style="font-weight: 400">Where it stands on v0.2.3, for an InnoDB primary feeding a DuckDB replica:</span></p>
<table>
<thead>
<tr>
<th><span style="font-weight: 400">Scenario</span></th>
<th><span style="font-weight: 400">Result</span></th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="font-weight: 400">INSERT / UPDATE / DELETE</span></td>
<td><span style="font-weight: 400">works, content matches</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">All column types (numeric, temporal, string, BLOB, NULL, unicode)</span></td>
<td><span style="font-weight: 400">works</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">ALTER ADD COLUMN / ADD INDEX, DROP TABLE</span></td>
<td><span style="font-weight: 400">works</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Transaction commit / rollback</span></td>
<td><span style="font-weight: 400">works, atomic</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Bulk LOAD DATA</span></td>
<td><span style="font-weight: 400">works</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Graceful restart, resume from GTID</span></td>
<td><span style="font-weight: 400">works</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">SIGKILL crash, no loss / no duplicates</span></td>
<td><span style="font-weight: 400">works</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">The things to keep in mind:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Create the replica tables as ENGINE=DuckDB yourself. A replicated CREATE TABLE keeps the primary&rsquo;s engine, so it will not turn into DuckDB on its own.</span></li>
</ul>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Replica tables need a primary key for UPDATE and DELETE.</span></li>
</ul>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">The applier goes row by row. That is fine for a normal OLTP change stream. It is not fine for keeping up with a primary that bulk-loads at full speed &ndash; the replica will fall behind.</span></li>
</ul>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Committed transactions are crash-safe, with one small gap. The engine holds a prepared-but-not-committed transaction in memory only, so a crash in the short window between prepare and commit can lose that single transaction. The applier commits right away, so the window is small, but it is not zero.</span></li>
</ul>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Blobs behave differently over replication than through a direct statement. A plain UPDATE of a BLOB or TEXT column has a known limit in the engine and does not apply. Over replication it does apply, because the row event carries a full before-and-after image instead of the shared buffer the direct path uses.</span></li>
</ul>
<p><span style="font-weight: 400">And the obvious one. This is an experiment. It is a functional result from a test harness on small data, not an HA or failover benchmark. We did not test multi-source replication, filters, or a real write rate.</span></p>
<h2><span style="font-weight: 400">Where it stands</span><a class="anchor-link" id="where-it-stands"></a></h2>
<p><span style="font-weight: 400">An InnoDB primary feeding a DuckDB replica works on v0.2.3. Inserts, updates, deletes, every common type, schema changes, transactions, bulk load &ndash; they all replicate and match, and it comes back clean from both a graceful restart and a hard kill. The one real bug, silent data loss on every replicated transaction, is found, understood, fixed, and covered by tests.</span></p>
<p><span style="font-weight: 400">It is not production-ready, and we do not treat it as such. But the idea holds up. Point normal MySQL replication at a DuckDB replica, and you get an analytics copy that keeps itself in sync.</span></p>
<p>The post <a href="https://www.percona.com/blog/replicating-from-innodb-into-a-duckdb-storage-engine/">Replicating from InnoDB into a DuckDB storage engine</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p><a href="https://www.percona.com/blog/replicating-from-innodb-into-a-duckdb-storage-engine/">Replicating from InnoDB into a DuckDB storage engine</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB 13.1 Feature in Focus: JSON Operators and JSON_TABLE Improvements</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-13-1-feature-in-focus-json-operators-and-json_table-improvements/" />
      <id>https://mariadb.org/mariadb-13-1-feature-in-focus-json-operators-and-json_table-improvements/</id>
      <updated>2026-08-13T09:45:51+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>JSON support in MariaDB has improved significantly over the years.<br />
We have functions to create JSON documents, extract values, modify objects, inspect arrays, compare documents, and even transform JSON into relational rows using JSON_TABLE(). …<br />
Continue reading \"MariaDB 13.1 Feature in Focus: JSON Operators and JSON_TABLE Improvements\"<br />
MariaDB 13.1 Feature in Focus: JSON Operators and JSON_TABLE Improvements appeared first on MariaDB.org</p>
<p><a href="https://mariadb.org/mariadb-13-1-feature-in-focus-json-operators-and-json_table-improvements/">MariaDB 13.1 Feature in Focus: JSON Operators and JSON_TABLE Improvements</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>JSON support in MariaDB has improved significantly over the years.<br>
We have functions to create JSON documents, extract values, modify objects, inspect arrays, compare documents, and even transform JSON into relational rows using <a href="https://mariadb.com/docs/server/reference/sql-functions/special-functions/json-functions/json_table">JSON_TABLE()</a>. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-13-1-feature-in-focus-json-operators-and-json_table-improvements/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB 13.1 Feature in Focus: JSON Operators and JSON_TABLE Improvements&rdquo;</span></a></p>
<p><a href="https://mariadb.org/mariadb-13-1-feature-in-focus-json-operators-and-json_table-improvements/">MariaDB 13.1 Feature in Focus: JSON Operators and JSON_TABLE Improvements</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>

<p><a href="https://mariadb.org/mariadb-13-1-feature-in-focus-json-operators-and-json_table-improvements/">MariaDB 13.1 Feature in Focus: JSON Operators and JSON_TABLE Improvements</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Percona for MongoDB: RHEL 10, Its Derivatives, and Debian 13 – On Both x86_64 and ARM</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/percona-for-mongodb-rhel-10-its-derivatives-and-debian-13-on-both-x86_64-and-arm/" />
      <id>https://www.percona.com/blog/percona-for-mongodb-rhel-10-its-derivatives-and-debian-13-on-both-x86_64-and-arm/</id>
      <updated>2026-08-11T12:15:41+00:00</updated>
      <author><name>Radoslaw Szulgo</name></author>
      <summary type="html"><![CDATA[<p>We’re happy to announce that Percona Server for MongoDB (PSMDB) 8.0.28-12 extends platform support to RHEL 10 and its derivatives (Oracle Linux 10, Rocky Linux 10, AlmaLinux 10, and other RHEL-compatible distributions) for both x86_64 and ARM (aarch64) architectures. This release also adds support for Debian 13 “Trixie” on x86_64 and ARM64. We’ll continue to … Continued<br />
The post Percona for MongoDB: RHEL 10, Its Derivatives, and Debian 13 – On Both x86_64 and ARM appeared first on Percona.</p>
<p><a href="https://www.percona.com/blog/percona-for-mongodb-rhel-10-its-derivatives-and-debian-13-on-both-x86_64-and-arm/">Percona for MongoDB: RHEL 10, Its Derivatives, and Debian 13 – On Both x86_64 and ARM</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">We&rsquo;re happy to announce that </span><b>Percona Server for MongoDB (PSMDB) 8.0.28-12</b><span style="font-weight: 400"> extends platform support to </span><b>RHEL 10 and its derivatives</b><span style="font-weight: 400"> (Oracle Linux 10, Rocky Linux 10, AlmaLinux 10, and other RHEL-compatible distributions) for both x86_64 and ARM (aarch64) architectures. This release also adds support for </span><b>Debian 13 &ldquo;Trixie&rdquo;</b><span style="font-weight: 400"> on x86_64 and ARM64. We&rsquo;ll continue to support that for 8.0, 8.3, and newer releases.</span></p>
<p><span style="font-weight: 400">This is an important step for anyone planning infrastructure refreshes around the latest Linux releases, and it&rsquo;s especially notable for teams evaluating ARM to cut infrastructure spend without sacrificing performance.</span></p>
<h2><span style="font-weight: 400">What&rsquo;s new in 8.0.28-12</span><a class="anchor-link" id="whats-new-in-8-0-28-12"></a></h2>
<p><span style="font-weight: 400">Starting with this release, Percona Server for MongoDB packages are available for:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">RHEL 10 and derivatives like Oracle Linux 10, Rocky Linux 10, AlmaLinux 10 on x86_64 and ARM (aarch64)</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Debian 13 &ldquo;Trixie&rdquo; on x86_64 and ARM64 (aarch64)</span></li>
</ul>
<p><span style="font-weight: 400">Additionally, starting this release, we&rsquo;ve included Software Bills of Materials (SBOMs) and Vulnerability Exploitability Exchange (VEX) for every release. SBOMs improve software supply chain transparency by documenting the components and dependencies included in a build. They are generated automatically as part of the release pipeline in the industry-standard </span><a href="https://cyclonedx.org/specification/overview/"><span style="font-weight: 400">CycloneDX</span></a><span style="font-weight: 400"> format. OpenVEX files are published on GitHub Pages and provide the exploitability status of known vulnerabilities. For comprehensive information, refer to our [documentation](../</span><a href="http://sbom.md"><span style="font-weight: 400">sbom.md</span></a><span style="font-weight: 400">).</span></p>
<p><span style="font-weight: 400">To learn more, see the full </span><a href="https://docs.percona.com/percona-server-for-mongodb/8.0/release_notes/8.0.28-12.html"><span style="font-weight: 400">release notes of Percona Server for MongoDB 8.0.28-12</span></a><span style="font-weight: 400">.</span></p>
<h2><span style="font-weight: 400">Ahead of upstream on Debian 13</span><a class="anchor-link" id="ahead-of-upstream-on-debian-13"></a></h2>
<p><span style="font-weight: 400">As of this writing (August 2026), </span><b>upstream MongoDB Community/Enterprise Server does not yet officially package or support Debian 13</b><span style="font-weight: 400">. Trixie isn&rsquo;t in MongoDB&rsquo;s supported platforms list, and the documented community workaround is to install the Debian 12 &ldquo;Bookworm&rdquo; build on Trixie hosts, since a native Trixie server build hasn&rsquo;t landed yet. PSMDB closes that gap now, with native Debian 13 packages rather than a buggy Bookworm build running out-of-distro.</span></p>
<h2><span style="font-weight: 400">Why ARM is worth a serious look for MongoDB workloads</span><a class="anchor-link" id="why-arm-is-worth-a-serious-look-for-mongodb-workloads"></a></h2>
<p><span style="font-weight: 400">We have heard multiple times from you directly, via our forum, or on Reddit about the Interest in ARM for database workloads. Over the last few years, adoption has moved well past the experimental phase to resilient production readiness. Our adoption telemetry data show nearly 3x as many ARM instances over the last 12 months!</span></p>
<p><a href="https://www.percona.com/wp-content/uploads/2026/08/pmm-adoption-arm.png"><img loading="lazy" decoding="async" class="aligncenter wp-image-51629 size-2048x2048" src="https://www.percona.com/wp-content/uploads/2026/08/pmm-adoption-arm-2048x673.png" alt="" width="2048" height="673"></a></p>
<p><span style="font-weight: 400">I can see a number of benefits and reasons why our community users and customers adopted ARM over AMD or Intel CPU architectures:</span></p>
<ul>
<li style="font-weight: 400"><b>Lower infrastructure costs.</b><span style="font-weight: 400"> Cloud ARM instances, such as AWS Graviton, are commonly cited as running 20&ndash;40% cheaper than comparable x86 instances at similar or better performance (</span><a href="https://sanj.dev/post/arm-vs-x86-cloud-2025/"><span style="font-weight: 400">sanj.dev, &ldquo;ARM vs x86 Cloud: Which Architecture is Cheaper in 2025?&rdquo;</span></a><span style="font-weight: 400">), and benchmarking write-ups report up to 60% lower energy consumption for compute-intensive workloads on Graviton compared to x86 equivalents (</span><a href="https://www.nops.io/blog/are-you-missing-out-on-aws-graviton-cost-savings/"><span style="font-weight: 400">nOps, &ldquo;Are you missing out on AWS Graviton Cost Savings?&rdquo;</span></a><span style="font-weight: 400">).</span></li>
<li style="font-weight: 400"><b>Competitive, and often better, throughput.</b> <a href="https://www.usage.ai/blogs/aws/reserved-instances/rds/postgresql/graviton-instances/"><span style="font-weight: 400">RDS PostgreSQL Graviton&rdquo; benchmark analysis</span></a><span style="font-weight: 400"> shows Graviton4 delivering up to 40% better performance for OLTP-style workloads versus the previous Graviton3 generation. </span><a href="https://www.velodb.io/blog/apache-doris-achieves-70-better-price-performance"><span style="font-weight: 400">Apache Doris Delivers 70% Better Value on AWS Graviton</span></a><span style="font-weight: 400">. I can see MongoDB database achieving a similar level of performance-to-cost gain.&nbsp;</span></li>
<li style="font-weight: 400"><b>Memory bandwidth is a real differentiator.</b><span style="font-weight: 400"> Graviton3&rsquo;s memory bandwidth (cited at roughly 115&ndash;120 GB/s) is reported to significantly outpace typical Intel Xeon configurations (roughly 60&ndash;70 GB/s) and AMD EPYC (roughly 80&ndash;90 GB/s) in independent comparisons (</span><a href="https://byteiota.com/arm-vs-x86-cloud-2025-performance-cost-benchmark/"><span style="font-weight: 400">byteiota, &ldquo;ARM vs x86 Cloud: 2025 Performance &amp; Cost Benchmark&rdquo;</span></a><span style="font-weight: 400">), which matters for memory-hungry workloads like MongoDB&rsquo;s WiredTiger cache and in-memory working sets.</span></li>
</ul>
<p><i><span style="font-weight: 400">(Note: The above figures come from third-party blogs and vendor case studies rather than peer-reviewed benchmarks. Treat them as directional evidence that ARM is worth evaluating, not a guarantee of results for your specific workload. For the official recommendation based on your workload, reach out to Percona)</span></i></p>
<p><span style="font-weight: 400">Netflix has publicly stated that it saves over $15 million annually after migrating video encoding workloads to Graviton, while also seeing faster processing times, and other large-scale AWS customers have reported double-digit percentage reductions in compute costs after moving meaningful portions of their backend fleets to ARM (</span><a href="https://byteiota.com/arm-vs-x86-cloud-2025-performance-cost-benchmark/"><span style="font-weight: 400">byteiota</span></a><span style="font-weight: 400">; </span><a href="https://sanj.dev/post/arm-vs-x86-cloud-2025/"><span style="font-weight: 400">sanj.dev</span></a><span style="font-weight: 400">).</span></p>
<h2><span style="font-weight: 400">What to watch out for</span><a class="anchor-link" id="what-to-watch-out-for"></a></h2>
<p><span style="font-weight: 400">One RHEL 10 detail to keep in mind when planning a migration: Red Hat raised the CPU baseline for x86_64 to the </span><b>x86-64-v3</b><span style="font-weight: 400"> microarchitecture level, meaning the processor needs to support instruction sets such as AVX2 (</span><a href="https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/10/html/considerations_in_adopting_rhel_10/architectures"><span style="font-weight: 400">Red Hat, RHEL 10 architecture documentation</span></a><span style="font-weight: 400">; </span><a href="https://vinfrastructure.it/2025/05/red-hat-enterprise-linux-10-0/"><span style="font-weight: 400">vInfrastructure Blog</span></a><span style="font-weight: 400">). On the ARM side, RHEL 10 targets the ARMv8.0-A baseline (</span><a href="https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/10/html/considerations_in_adopting_rhel_10/architectures"><span style="font-weight: 400">Red Hat documentation</span></a><span style="font-weight: 400">). This applies equally to Oracle Linux 10, Rocky Linux 10, and AlmaLinux 10, since they build from the same upstream sources. I highly recommend checking your current infrastructure before planning the move, especially for older bare-metal fleets.</span></p>
<p><span style="font-weight: 400">In general, but especially on ARM, remember that performance is workload-dependent. Some code paths and workloads leaning on x64-specific instruction extensions may not see the same gains as throughput-oriented, multi-threaded workloads on ARM. Benchmarking your own query patterns and index-heavy operations before a full cutover is essential. General benchmarks are a good signal, not a guarantee.</span></p>
<h2><span style="font-weight: 400">Percona can help you get there</span><a class="anchor-link" id="percona-can-help-you-get-there"></a></h2>
<p><span style="font-weight: 400">Migrating a production MongoDB deployment to a different infrastructure is a project with real decision points. There are a number of questions to answer around: Hardware or instance selection, driver and tooling compatibility, benchmarking against your actual workload, and a rollback plan. The Percona Services team helps customers plan and execute exactly this kind of migration. We start from initial architecture assessment and proof-of-concept benchmarking and go through to production cutover and post-migration tuning.</span></p>
<p><span style="font-weight: 400">If you&rsquo;re weighing a move to ARM, or just want to get onto RHEL 10 or Debian 13 without surprises, </span><a href="https://www.percona.com/about/contact"><span style="font-weight: 400">reach out to Percona</span></a><span style="font-weight: 400"> to talk through your environment.</span></p>
<p>The post <a href="https://www.percona.com/blog/percona-for-mongodb-rhel-10-its-derivatives-and-debian-13-on-both-x86_64-and-arm/">Percona for MongoDB: RHEL 10, Its Derivatives, and Debian 13 &ndash; On Both x86_64 and ARM</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p><a href="https://www.percona.com/blog/percona-for-mongodb-rhel-10-its-derivatives-and-debian-13-on-both-x86_64-and-arm/">Percona for MongoDB: RHEL 10, Its Derivatives, and Debian 13 – On Both x86_64 and ARM</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Making the PostgreSQL Documentation Even Better</title>
      <link rel="alternate" type="text/html" href="https://www.fromdual.com/blog/postgresql/improve-postgresql-documentation/" />
      <id>https://www.fromdual.com/blog/postgresql/improve-postgresql-documentation/</id>
      <updated>2026-08-11T06:40:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>While working on our project “PostgreSQL for Dolphins and Sea Lions,” I pored over the PostgreSQL documentation on replication.<br />
Since I’m not quite up to speed on this topic yet, I like to look up certain terms (parameters, functions, etc.) every now and then to see exactly what they mean or how they work (RTFM!). That’s exactly why links were originally invented—the very thing that first made Gopher and later the Internet/WWW (http) so popular.<br />
Unfortunately, however, these links are often missing from the documentation in question, which disrupts the flow of reading.<br />
Fortunately, though, PostgreSQL is an open-source project, and contributions are highly encouraged! So instead of just grumbling about the documentation, I could add the missing links myself. But how exactly do I go about doing that in an ecosystem that’s new to me and therefore still a bit unfamiliar? An article by Elizabeth Christensen from Crunchy Data titled Contributing to Postgres 101: A Beginner’s Experience helped me get started.<br />
Since I have absolutely no programming experience myself, I see improving the documentation as a great opportunity to actively contribute to the project and help out…<br />
Improving the PostgreSQL Documentation<br />
The PostgreSQL documentation is stored directly in the server repository. So, first, let’s download the Git repository from the PostgreSQL server:<br />
$ git clone http://git.postgresql.org/git/postgresql.git<br />
The next challenge is finding the right file:<br />
$ cd postgresql/doc/src/sgml<br />
The grep command, in all its forms, comes in handy here:<br />
$ grep -r \'Planning for High Availability\' *.sgml<br />
high-availability.sgml: Planning for High Availability<br />
The correct document appears to be high-availability.sgml. The PostgreSQL documentation itself is written in SGML, which is similar to HTML and not particularly difficult to learn.<br />
These SGML files can be easily read and edited using your editor of choice with appropriate code highlighting.<br />
Next, we need to check the individual keywords to see if they’ve already been correctly marked up and, if so, add links to them:</p>
<p>					Keyword<br />
					Markup<br />
					Links</p>
<p>					synchronous_standby_names<br />
					synchronous_standby_names</p>
<p>					archive_command<br />
					archive_command</p>
<p>					archive_library<br />
					archive_library</p>
<p>					synchronous_commit<br />
					synchronous_commit</p>
<p>					pg_receivewal<br />
					pg_receivewal</p>
<p>					pg_recvlogical<br />
					pg_recvlogical</p>
<p>					pg_backup_stop<br />
					pg_backup_stop()<br />
					pg_backup_stop()</p>
<p>					pg_backup_start<br />
					pg_backup_start()<br />
					pg_backup_start()</p>
<p>					pg_switch_wal<br />
					pg_switch_wal()<br />
					pg_switch_wal()</p>
<p>Note: Keep in mind that keywords are written with an “_” (underscore) and links with a “-” (hyphen).<br />
While building the documentation, it was also noticed that some link targets (id) hadn’t been set at all, so these had to be adjusted as well:</p>
<p>-<br />
+ </p>
<p> pg_backup_start<br />
Quality Assurance<br />
Once all changes have been made, it’s time for quality control. To do this, build the documentation locally:<br />
$ cd postgresql<br />
$ ./configure<br />
$ cd doc<br />
$ make<br />
Exact details on how this works are described here.<br />
If the build finds any errors, they will be displayed and the build will be aborted. If everything runs smoothly, you can now use your browser of choice to check whether everything actually works as intended:<br />
$ firefox src/sgml/html/warm-standby.html<br />
Something else I discovered later:</p>
<p>Building the documentation can take very long. But there is a method to just check the correct syntax of the documentation files, which only takes a few seconds: [ 5 ]</p>
<p>$ make check<br />
make -C ../src/backend generated-headers<br />
make[1]: Entering directory \'/home/oli/fromdual/postgresql/docu/postgresql/src/backend\'<br />
make -C ../include/catalog generated-headers<br />
make[2]: Entering directory \'/home/oli/fromdual/postgresql/docu/postgresql/src/include/catalog\'<br />
make[2]: Nothing to be done for \'generated-headers\'.<br />
make[2]: Leaving directory \'/home/oli/fromdual/postgresql/docu/postgresql/src/include/catalog\'<br />
make -C nodes generated-header-symlinks<br />
make[2]: Entering directory \'/home/oli/fromdual/postgresql/docu/postgresql/src/backend/nodes\'<br />
make[2]: Nothing to be done for \'generated-header-symlinks\'.<br />
make[2]: Leaving directory \'/home/oli/fromdual/postgresql/docu/postgresql/src/backend/nodes\'<br />
make -C utils generated-header-symlinks<br />
make[2]: Entering directory \'/home/oli/fromdual/postgresql/docu/postgresql/src/backend/utils\'<br />
make -C adt jsonpath_gram.h<br />
make[3]: Entering directory \'/home/oli/fromdual/postgresql/docu/postgresql/src/backend/utils/adt\'<br />
make[3]: \'jsonpath_gram.h\' is up to date.<br />
make[3]: Leaving directory \'/home/oli/fromdual/postgresql/docu/postgresql/src/backend/utils/adt\'<br />
make[2]: Leaving directory \'/home/oli/fromdual/postgresql/docu/postgresql/src/backend/utils\'<br />
make[1]: Leaving directory \'/home/oli/fromdual/postgresql/docu/postgresql/src/backend\'<br />
rm -rf \'/home/oli/fromdual/postgresql/docu/postgresql\'/tmp_install<br />
/usr/bin/mkdir -p \'/home/oli/fromdual/postgresql/docu/postgresql\'/tmp_install/log<br />
make -C \'..\' DESTDIR=\'/home/oli/fromdual/postgresql/docu/postgresql\'/tmp_install install >\'/home/oli/fromdual/postgresql/docu/postgresql\'/tmp_install/log/install.log 2 >&#038;1<br />
make -j1 checkprep > >\'/home/oli/fromdual/postgresql/docu/postgresql\'/tmp_install/log/install.log 2 >&#038;1<br />
PATH=\"/home/oli/fromdual/postgresql/docu/postgresql/tmp_install/usr/local/pgsql/bin:/home/oli/fromdual/postgresql/docu/postgresql/doc:$PATH\" LD_LIBRARY_PATH=\"/home/oli/fromdual/postgresql/docu/postgresql/tmp_install/usr/local/pgsql/lib:$LD_LIBRARY_PATH\" INITDB_TEMPLATE=\'/home/oli/fromdual/postgresql/docu/postgresql\'/tmp_install/initdb-template initdb --auth trust --no-sync --no-instructions --lc-messages=C --no-clean \'/home/oli/fromdual/postgresql/docu/postgresql\'/tmp_install/initdb-template > >\'/home/oli/fromdual/postgresql/docu/postgresql\'/tmp_install/log/initdb-template.log 2 >&#038;1<br />
Submitting the Patch<br />
If everything works as intended and to your satisfaction, you can then proceed to create the patch and submit it:<br />
$ git commit -m \'some references on variables and functions added\'<br />
$ git format-patch -1 HEAD<br />
This creates a file containing the commit comment: 0001-some-references-on-variables-and-functions-added.patch.<br />
Apparently, in the PostgreSQL project, you don’t create a merge request to incorporate the patch back into the source code; instead, the patch must be sent to the appropriate mailing list and then merged into the main branch by a developer with merge/commit privileges. I’ve now agreed with “my” committer that we’ll hold the discussion about my patch on the pgsql-docs mailing list.<br />
Let’s see how things go from here and how far I get with my patch…<br />
This page was translated using deepl.com.</p>
<p><a href="https://www.fromdual.com/blog/postgresql/improve-postgresql-documentation/">Making the PostgreSQL Documentation Even Better</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>While working on our project &ldquo;PostgreSQL for Dolphins and Sea Lions,&rdquo; I pored over the PostgreSQL documentation on <a href="https://www.postgresql.org/docs/current/warm-standby.html" target="_blank" title="Log-Shipping Standby Servers">replication</a>.</p>
<p>Since I&rsquo;m not quite up to speed on this topic yet, I like to look up certain terms (parameters, functions, etc.) every now and then to see exactly what they mean or how they work (<a href="https://en.wikipedia.org/wiki/RTFM" target="_blank">RTFM</a>!). That&rsquo;s exactly why <strong>links</strong> were originally invented&mdash;the very thing that first made <a href="https://en.wikipedia.org/wiki/Gopher_(protocol)" target="_blank">Gopher</a> and later the Internet/WWW (http) so popular.</p>
<p>Unfortunately, however, these links are often missing from the documentation in question, which disrupts the flow of reading.</p>
<p>Fortunately, though, PostgreSQL is an open-source project, and contributions are highly encouraged! So instead of just grumbling about the documentation, I could add the missing links myself. But how exactly do I go about doing that in an ecosystem that&rsquo;s new to me and therefore still a bit unfamiliar? An article by Elizabeth Christensen from Crunchy Data titled <a href="https://www.crunchydata.com/blog/contributing-to-postgres-101-a-beginners-experience" target="_blank">Contributing to Postgres 101: A Beginner&rsquo;s Experience</a> helped me get started.</p>
<p>Since I have absolutely no programming experience myself, I see improving the documentation as a great opportunity to actively contribute to the project and help out&hellip;</p>
<h2 id="improving-the-postgresql-documentation">Improving the PostgreSQL Documentation<a class="anchor-link" id="improving-the-postgresql-documentation"></a></h2>
<p>The PostgreSQL documentation is stored directly in the server repository. So, first, let&rsquo;s download the Git repository from the PostgreSQL server:</p>
<pre><code>$ git clone http://git.postgresql.org/git/postgresql.git
</code></pre>
<p>The next challenge is finding the right file:</p>
<pre><code>$ cd postgresql/doc/src/sgml
</code></pre>
<p>The <code>grep</code> command, in all its forms, comes in handy here:</p>
<pre><code>$ grep -r 'Planning for High Availability' *.sgml
high-availability.sgml: &lt;title&gt;Planning for High Availability&lt;/title&gt;
</code></pre>
<p>The correct document appears to be <code>high-availability.sgml</code>. The PostgreSQL documentation itself is written in <a href="https://en.wikipedia.org/wiki/Standard_Generalized_Markup_Language" target="_blank">SGML</a>, which is similar to HTML and not particularly difficult to learn.</p>
<p>These SGML files can be easily read and edited using your editor of choice with appropriate code highlighting.</p>
<p>Next, we need to check the individual keywords to see if they&rsquo;ve already been correctly marked up and, if so, add links to them:</p>
<table>
<thead>
<tr>
<th>Keyword</th>
<th>Markup</th>
<th>Links</th>
</tr>
</thead>
<tbody>
<tr>
<td>synchronous_standby_names</td>
<td><b>&lt;varname&gt;</b>synchronous_standby_names<b>&lt;/varname&gt;</b></td>
<td>&lt;xref linkend="<strong>guc-synchronous-standby-names</strong>"/&gt;</td>
</tr>
<tr>
<td>archive_command</td>
<td><b>&lt;varname&gt;</b>archive_command<b>&lt;/varname&gt;</b></td>
<td>&lt;xref linkend="<strong>guc-archive-command</strong>"/&gt;</td>
</tr>
<tr>
<td>archive_library</td>
<td><b>&lt;varname&gt;</b>archive_library<b>&lt;/varname&gt;</b></td>
<td>&lt;xref linkend="<strong>guc-archive-library</strong>"/&gt;</td>
</tr>
<tr>
<td>synchronous_commit</td>
<td><b>&lt;varname&gt;</b>synchronous_commit<b>&lt;/varname&gt;</b></td>
<td>&lt;xref linkend="<strong>guc-synchronous-commit</strong>"/&gt;</td>
</tr>
<tr>
<td>pg_receivewal</td>
<td><b>&lt;command&gt;</b>pg_receivewal<b>&lt;/command&gt;</b></td>
<td>&lt;xref linkend="<strong>app-pgreceivewal</strong>"/&gt;</td>
</tr>
<tr>
<td>pg_recvlogical</td>
<td><b>&lt;command&gt;</b>pg_recvlogical<b>&lt;/command&gt;</b></td>
<td>&lt;xref linkend="<strong>app-pgrecvlogical</strong>"/&gt;</td>
</tr>
<tr>
<td>pg_backup_stop</td>
<td><b>&lt;function&gt;</b>pg_backup_stop()<b>&lt;/function&gt;</b></td>
<td><b>&lt;link linkend=&ldquo;pg-backup-stop&rdquo;&gt;</b>&lt;function&gt;pg_backup_stop()&lt;/function&gt;<b>&lt;/link&gt;</b></td>
</tr>
<tr>
<td>pg_backup_start</td>
<td><b>&lt;function&gt;</b>pg_backup_start()<b>&lt;/function&gt;</b></td>
<td><b>&lt;link linkend=&ldquo;pg-backup-start&rdquo;&gt;</b>&lt;function&gt;pg_backup_start()&lt;/function&gt;<b>&lt;/link&gt;</b></td>
</tr>
<tr>
<td>pg_switch_wal</td>
<td><b>&lt;function&gt;</b>pg_switch_wal()<b>&lt;/function&gt;</b></td>
<td><b>&lt;link linkend=&ldquo;pg_switch_wal&rdquo;&gt;</b>&lt;function&gt;pg_switch_wal()&lt;/function&gt;<b>&lt;/link&gt;</b></td>
</tr>
</tbody>
</table>
<p><strong>Note</strong>: Keep in mind that keywords are written with an &ldquo;_&rdquo; (underscore) and links with a &ldquo;-&rdquo; (hyphen).</p>
<p>While building the documentation, it was also noticed that some link targets (<code>id</code>) hadn&rsquo;t been set at all, so these had to be adjusted as well:</p>
<pre><code> &lt;row&gt;
- &lt;entry role="func_table_entry"&gt;&lt;para role="func_signature"&gt;
+ &lt;entry id="pg-backup-start" role="func_table_entry"&gt;&lt;para role="func_signature"&gt;
 &lt;indexterm&gt;
 &lt;primary&gt;pg_backup_start&lt;/primary&gt;
</code></pre>
<h2 id="quality-assurance">Quality Assurance<a class="anchor-link" id="quality-assurance"></a></h2>
<p>Once all changes have been made, it&rsquo;s time for quality control. To do this, build the documentation locally:</p>
<pre><code>$ cd postgresql
$ ./configure
$ cd doc
$ make
</code></pre>
<p>Exact details on how this works are described <a href="https://www.postgresql.org/docs/18/docguide-build.html" target="_blank" title="Building the Documentation with Make">here</a>.</p>
<p>If the build finds any errors, they will be displayed and the build will be aborted. If everything runs smoothly, you can now use your browser of choice to check whether everything actually works as intended:</p>
<pre><code>$ firefox src/sgml/html/warm-standby.html
</code></pre>
<p>Something else I discovered later:</p>
<blockquote>
<p>Building the documentation can take very long. But there is a method to just check the correct syntax of the documentation files, which only takes a few seconds: [ <a href="https://www.postgresql.org/docs/18/docguide-build.html#DOCGUIDE-BUILD-SYNTAX-CHECK" target="_blank" title="Syntax Check">5</a> ]</p>
</blockquote>
<pre><code>$ make check
make -C ../src/backend generated-headers
make[1]: Entering directory '/home/oli/fromdual/postgresql/docu/postgresql/src/backend'
make -C ../include/catalog generated-headers
make[2]: Entering directory '/home/oli/fromdual/postgresql/docu/postgresql/src/include/catalog'
make[2]: Nothing to be done for 'generated-headers'.
make[2]: Leaving directory '/home/oli/fromdual/postgresql/docu/postgresql/src/include/catalog'
make -C nodes generated-header-symlinks
make[2]: Entering directory '/home/oli/fromdual/postgresql/docu/postgresql/src/backend/nodes'
make[2]: Nothing to be done for 'generated-header-symlinks'.
make[2]: Leaving directory '/home/oli/fromdual/postgresql/docu/postgresql/src/backend/nodes'
make -C utils generated-header-symlinks
make[2]: Entering directory '/home/oli/fromdual/postgresql/docu/postgresql/src/backend/utils'
make -C adt jsonpath_gram.h
make[3]: Entering directory '/home/oli/fromdual/postgresql/docu/postgresql/src/backend/utils/adt'
make[3]: 'jsonpath_gram.h' is up to date.
make[3]: Leaving directory '/home/oli/fromdual/postgresql/docu/postgresql/src/backend/utils/adt'
make[2]: Leaving directory '/home/oli/fromdual/postgresql/docu/postgresql/src/backend/utils'
make[1]: Leaving directory '/home/oli/fromdual/postgresql/docu/postgresql/src/backend'
rm -rf '/home/oli/fromdual/postgresql/docu/postgresql'/tmp_install
/usr/bin/mkdir -p '/home/oli/fromdual/postgresql/docu/postgresql'/tmp_install/log
make -C '..' DESTDIR='/home/oli/fromdual/postgresql/docu/postgresql'/tmp_install install &gt;'/home/oli/fromdual/postgresql/docu/postgresql'/tmp_install/log/install.log 2&gt;&amp;1
make -j1 checkprep &gt;&gt;'/home/oli/fromdual/postgresql/docu/postgresql'/tmp_install/log/install.log 2&gt;&amp;1
PATH="/home/oli/fromdual/postgresql/docu/postgresql/tmp_install/usr/local/pgsql/bin:/home/oli/fromdual/postgresql/docu/postgresql/doc:$PATH" LD_LIBRARY_PATH="/home/oli/fromdual/postgresql/docu/postgresql/tmp_install/usr/local/pgsql/lib:$LD_LIBRARY_PATH" INITDB_TEMPLATE='/home/oli/fromdual/postgresql/docu/postgresql'/tmp_install/initdb-template initdb --auth trust --no-sync --no-instructions --lc-messages=C --no-clean '/home/oli/fromdual/postgresql/docu/postgresql'/tmp_install/initdb-template &gt;&gt;'/home/oli/fromdual/postgresql/docu/postgresql'/tmp_install/log/initdb-template.log 2&gt;&amp;1
</code></pre>
<h2 id="submitting-the-patch">Submitting the Patch<a class="anchor-link" id="submitting-the-patch"></a></h2>
<p>If everything works as intended and to your satisfaction, you can then proceed to create the patch and submit it:</p>
<pre><code>$ git commit -m 'some references on variables and functions added'
$ git format-patch -1 HEAD
</code></pre>
<p>This creates a file containing the commit comment: <code>0001-some-references-on-variables-and-functions-added.patch</code>.</p>
<p>Apparently, in the PostgreSQL project, you don&rsquo;t create a merge request to incorporate the patch back into the source code; instead, the patch must be sent to the appropriate mailing list and then merged into the main branch by a developer with merge/commit privileges. I&rsquo;ve now agreed with &ldquo;my&rdquo; committer that we&rsquo;ll hold the discussion about my patch on the <a href="https://www.postgresql.org/list/pgsql-docs/" target="_blank">pgsql-docs</a> mailing list.</p>
<p>Let&rsquo;s see how things go from here and how far I get with my patch&hellip;</p>
<p>This page was translated using <a href="https://www.deepl.com/en/translator" target="_blank">deepl.com</a>.</p>

<p><a href="https://www.fromdual.com/blog/postgresql/improve-postgresql-documentation/">Making the PostgreSQL Documentation Even Better</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Foundation is pleased to welcome Auree as a Silver Sponsor.</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-foundation-is-pleased-to-welcome-auree-as-a-silver-sponsor/" />
      <id>https://mariadb.org/mariadb-foundation-is-pleased-to-welcome-auree-as-a-silver-sponsor/</id>
      <updated>2026-08-10T15:34:54+00:00</updated>
      <author><name>Anna Widenius</name></author>
      <summary type="html"><![CDATA[<p>Auree is building a cloud-independent platform for deploying and operating highly available open source databases, including MariaDB, inside customers’ own cloud accounts. Its Bring Your Own Cloud model allows organisations to choose their cloud provider, region, infrastructure size, and security environment while Auree automates database provisioning, monitoring, backups, clustering, and failover. …<br />
Continue reading \"MariaDB Foundation is pleased to welcome Auree as a Silver Sponsor.\"<br />
MariaDB Foundation is pleased to welcome Auree as a Silver Sponsor. appeared first on MariaDB.org</p>
<p><a href="https://mariadb.org/mariadb-foundation-is-pleased-to-welcome-auree-as-a-silver-sponsor/">MariaDB Foundation is pleased to welcome Auree as a Silver Sponsor.</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><a href="https://www.auree.com/" data-type="link" data-id="https://www.auree.com/">Auree</a> is building a cloud-independent platform for deploying and operating highly available open source databases, including MariaDB, inside customers&rsquo; own cloud accounts. Its Bring Your Own Cloud model allows organisations to choose their cloud provider, region, infrastructure size, and security environment while Auree automates database provisioning, monitoring, backups, clustering, and failover. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-foundation-is-pleased-to-welcome-auree-as-a-silver-sponsor/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Foundation is pleased to welcome Auree as a Silver Sponsor.&rdquo;</span></a></p>
<p><a href="https://mariadb.org/mariadb-foundation-is-pleased-to-welcome-auree-as-a-silver-sponsor/">MariaDB Foundation is pleased to welcome Auree as a Silver Sponsor.</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>

<p><a href="https://mariadb.org/mariadb-foundation-is-pleased-to-welcome-auree-as-a-silver-sponsor/">MariaDB Foundation is pleased to welcome Auree as a Silver Sponsor.</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MySQL and MariaDB High Availability vs. Disaster Recovery: What’s the Difference (and Why It Matters)</title>
      <link rel="alternate" type="text/html" href="https://www.continuent.com/resources/blog/mysql-ha-vs-dr-what-is-the-difference" />
      <id>https://www.continuent.com/resources/blog/mysql-ha-vs-dr-what-is-the-difference</id>
      <updated>2026-08-08T09:51:09+00:00</updated>
      <author><name>Continuent Team</name></author>
      <summary type="html"><![CDATA[<p>High availability keeps MySQL and MariaDB applications running through routine local failures, while disaster recovery restores service after a site or regional outage. This article explains how RTO, RPO, distance, synchronous replication and asynchronous replication shape each strategy, and how Continuent Tungsten Cluster combines local HA with multi-site DR.</p>
<p><a href="https://www.continuent.com/resources/blog/mysql-ha-vs-dr-what-is-the-difference">MySQL and MariaDB High Availability vs. Disaster Recovery: What’s the Difference (and Why It Matters)</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>High availability keeps MySQL and MariaDB applications running through routine local failures, while disaster recovery restores service after a site or regional outage. This article explains how RTO, RPO, distance, synchronous replication and asynchronous replication shape each strategy, and how Continuent Tungsten Cluster combines local HA with multi-site DR.</p>

<p><a href="https://www.continuent.com/resources/blog/mysql-ha-vs-dr-what-is-the-difference">MySQL and MariaDB High Availability vs. Disaster Recovery: What’s the Difference (and Why It Matters)</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Node.js Connector 3.5.4 and 3.4.7 now available</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/mariadb-node-js-connector-3-5-4-and-3-4-7-now-available/" />
      <id>https://mariadb.com/resources/blog/mariadb-node-js-connector-3-5-4-and-3-4-7-now-available/</id>
      <updated>2026-08-07T17:48:20+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.4, 3.4.7, 3.3.4, and 3.2.5 GA releases. Download […]</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-node-js-connector-3-5-4-and-3-4-7-now-available/">MariaDB Node.js Connector 3.5.4 and 3.4.7 now available</a> appeared first on <a 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.4, 3.4.7, 3.3.4, and 3.2.5 GA releases. Download Now MariaDB Connector/Node.js 3.5.4 is a Stable (GA) release. Notable changes in this release include: Two connection options changed since 3.5.3: MariaDB Connector/Node.js 3.4.7 is a Stable (GA)&hellip;</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-node-js-connector-3-5-4-and-3-4-7-now-available/" rel="nofollow">Source</a></p>

<p><a href="https://mariadb.com/resources/blog/mariadb-node-js-connector-3-5-4-and-3-4-7-now-available/">MariaDB Node.js Connector 3.5.4 and 3.4.7 now available</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>The open way of Percona Search for MongoDB</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/the-open-way-of-percona-search-for-mongodb/" />
      <id>https://www.percona.com/blog/the-open-way-of-percona-search-for-mongodb/</id>
      <updated>2026-08-07T14:55:06+00:00</updated>
      <author><name>Radoslaw Szulgo</name></author>
      <summary type="html"><![CDATA[<p>Percona Search for MongoDB is Percona’s downstream distribution of mongot, the search engine that provides MongoDB’s full-text and vector search capabilities. With this addition, you can power your applications with AI and advanced search techniques – anywhere, and without vendor lock-in. It’s the same search engine that powers MongoDB Atlas Search.  Percona Search for MongoDB … Continued<br />
The post The open way of Percona Search for MongoDB appeared first on Percona.</p>
<p><a href="https://www.percona.com/blog/the-open-way-of-percona-search-for-mongodb/">The open way of Percona Search for MongoDB</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">Percona Search for MongoDB is Percona&rsquo;s downstream distribution of </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400">, the search engine that provides MongoDB&rsquo;s full-text and vector search capabilities. With this addition, you can power your applications with AI and advanced search techniques &ndash; anywhere, and without vendor lock-in. It&rsquo;s the same search engine that powers MongoDB Atlas Search.&nbsp;</span></p>
<p><span style="font-weight: 400">Percona Search for MongoDB runs as a separate </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400">&nbsp;process alongside Percona Server for MongoDB. The deployment topology determines how many </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400"> instances are required and how search requests are routed. Applications and users continue to connect to </span><span style="font-weight: 400">mongod</span><span style="font-weight: 400"> in a replica set, or to </span><span style="font-weight: 400">mongos</span><span style="font-weight: 400"> in a sharded cluster &ndash; never directly to </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400">.</span></p>
<p><a href="https://www.percona.com/wp-content/uploads/2026/08/mongot-deployment.png"><img loading="lazy" decoding="async" class="aligncenter wp-image-51373 size-full" src="https://www.percona.com/wp-content/uploads/2026/08/mongot-deployment.png" alt="" width="2547" height="1428"></a></p>
<p><span style="font-weight: 400">On behalf of the entire product and engineering team for MongoDB at Percona, I&rsquo;m pleased to share that we&rsquo;re starting a </span><b>Technical Preview with version 1.70.3-1.</b></p>
<h2>The way is open. Search should be too.<a class="anchor-link" id="the-way-is-open-search-should-be-too"></a></h2>
<p><span style="font-weight: 400">Before anything else, credit where it is due. MongoDB Inc. released full-text and vector search for self-managed deployments as GA in July 2026, and published the source for </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400"> &ndash; the same search engine that powers MongoDB Atlas Search. Opening up the engine behind a flagship commercial service is a significant step and precisely what makes this Technical Preview possible.&nbsp;</span></p>
<p><span style="font-weight: 400">What we want to add is the next layer of openness: freedom to choose your embedding model, to run inference where your data already lives, and to operate search with the same automated backup, monitoring, and Kubernetes tooling you already expect from every other tier of your database. That is the Percona way, and this post is our map for getting there.</span></p>
<h2><b>What we found in mongot</b><a class="anchor-link" id="what-we-found-in-mongot"></a></h2>
<p><span style="font-weight: 400">We went through the current release, reviewing everything needed to run it the way you want in production. Below is what we found, stated as plainly as we can, with the Percona plan attached to each item. None of these is a defect. They describe where today&rsquo;s release draws the line between the search engine and the operational layer around it. The operational layer is exactly where Percona has always done its work.</span></p>
<h3><b>Automatic embeddings and model choice&nbsp;</b><a class="anchor-link" id="automatic-embeddings-and-model-choice"></a></h3>
<p><span style="font-weight: 400">This is the big one, and it needs a little setup to see properly.</span></p>
<p><span style="font-weight: 400">Vector search doesn&rsquo;t search text. It searches vectors. If a user wants to find a document, they need to type a query that is first run through an embedding model and turned into an array of numbers. That conversion is not a one-time import step, either &ndash; it has to keep pace with your data, because a document whose text changed while its vector didn&rsquo;t is now quietly unfindable. No errors are raised. Query results simply get worse over time.</span></p>
<p><span style="font-weight: 400">There are two ways to handle it.</span></p>
<h4><b>Manually</b></h4>
<p><span style="font-weight: 400">You generate embeddings yourself and write the vectors into the document. This path is completely open, with no restrictions. It is also where a lot of vector search projects stall, because you have just taken ownership of an embedding pipeline: something has to watch inserts and updates, batch them, call a model, handle failures and retries, backfill the whole corpus when you change models, and guarantee every vector still matches the text next to it. That is a distributed-systems problem bolted onto a database that already solved distributed-systems problems. For a fixed corpus, you index once and forget &ndash; it is fine. For live operational data, it becomes a permanent tax on the team. In my humble opinion, it isn&rsquo;t the way to go for a production deployment at scale.</span></p>
<h4><b>Automatically</b></h4>
<p><span style="font-weight: 400">You declare which field holds your text and which model to use &ndash; the </span><span style="font-weight: 400">autoEmbed</span><span style="font-weight: 400"> type in the index definition &ndash; and the database generates the embeddings, keeps them in sync as the data changes, and accepts plain text at query time. This exists precisely because the manual path does not scale. It is the path the documentation leads with, the path every tutorial will use, and for most teams running search on data that changes, it is the only realistically maintainable option.</span></p>
<p><a href="https://www.percona.com/wp-content/uploads/2026/08/manual-vs-automatic-embeddings.png"><img loading="lazy" decoding="async" class="aligncenter wp-image-51386 size-full" src="https://www.percona.com/wp-content/uploads/2026/08/manual-vs-automatic-embeddings.png" alt="" width="2509" height="1370"></a></p>
<p><span style="font-weight: 400">Today, automatic embeddings are supported only with Voyage AI models: </span><span style="font-weight: 400">voyage-4-large</span><span style="font-weight: 400">, </span><span style="font-weight: 400">voyage-4</span><span style="font-weight: 400">, </span><span style="font-weight: 400">voyage-4-lite</span><span style="font-weight: 400">, and </span><span style="font-weight: 400">voyage-code-3</span><span style="font-weight: 400">. Three practical consequences follow from that.</span></p>
<ul>
<li style="font-weight: 400"><b>Your data travels to a third-party service.</b><span style="font-weight: 400"> Every document you index and every query your users type are sent to Voyage AI&rsquo;s cloud for processing. The support tickets, the patient notes, the contracts, the internal wiki &ndash; whatever you actually store &ndash; are handled outside your perimeter, from a database you self-host on hardware you own. Voyage AI does offer an on-premises deployment, which comes with its own licensing and costs. Without it, an air-gapped deployment cannot use automatic embeddings, and neither can teams working under data-residency obligations, which covers most of regulated Europe.</span></li>
<li style="font-weight: 400"><b>It&rsquo;s metered.</b><span style="font-weight: 400"> There is a free tier &ndash; 200 million tokens to get you started, less for specialized models &ndash; but it is capped on both volume and velocity, with requests and tokens per minute throttled. Beyond that, it runs roughly $0.02 to $0.12 per million tokens, on every reindex and every query your application serves.</span></li>
<li style="font-weight: 400"><b>The model is chosen for you.</b><span style="font-weight: 400"> Not the one that performs best in your language. Not the domain model your data science team fine-tuned. Not a smaller open-weights model that is good enough for your use case.</span></li>
</ul>
<h4><b>The Percona plan</b></h4>
<p><span style="font-weight: 400">We want automatic embeddings to be open, so you have a genuinely unlimited choice of models suited to your needs. We will start with everything that speaks to the OpenAI-compatible embeddings API, which already covers a large and growing ecosystem:</span></p>
<ul>
<li style="font-weight: 400"><b>Ollama</b><span style="font-weight: 400"> &ndash; local, free, 100+ open models including </span><span style="font-weight: 400">nomic-embed-text</span><span style="font-weight: 400">, </span><span style="font-weight: 400">mxbai-embed-large</span><span style="font-weight: 400">, and </span><span style="font-weight: 400">all-minilm</span></li>
<li style="font-weight: 400"><b>vLLM</b><span style="font-weight: 400"> &ndash; self-hosted GPU inference</span></li>
<li style="font-weight: 400"><b>llama.cpp server</b><span style="font-weight: 400"> &ndash; local CPU or GPU inference</span></li>
<li style="font-weight: 400"><b>LocalAI</b><span style="font-weight: 400"> and </span><b>LM Studio</b></li>
<li style="font-weight: 400"><b>Hugging Face Text Embeddings Inference (TEI)</b></li>
</ul>
<p><span style="font-weight: 400">Over time, we intend to widen that further, toward the 25,000-model catalog the open ecosystem has already built. Cloud providers remain available to teams that prefer them. They just stop being the only option.</span></p>
<h3><b>Reranking</b><a class="anchor-link" id="reranking"></a></h3>
<p><b>Reranking</b><span style="font-weight: 400"> is the second half of how serious retrieval works. Vector search is fast because the query and the documents are embedded separately and never actually compared &ndash; the model sees your query, sees a document, and never sees them side by side. That approximation is what makes it possible to search millions of documents in milliseconds, and it is also why the top result is often merely in the right neighborhood rather than right. A reranker fixes that: it takes the top 50 or 100 candidates and runs each through a model that reads the query and the document together, scoring genuine relevance rather than vector proximity. In practice, this is usually the single largest accuracy improvement available in a retrieval pipeline, and it matters most for RAG, where the language model only ever sees the top handful of results. If the passage that answers the question is sitting at rank eight, your application behaves as though the answer does not exist.</span></p>
<p><span style="font-weight: 400">The </span><span style="font-weight: 400">$rerank</span><span style="font-weight: 400"> stage is available only on MongoDB Atlas.</span></p>
<h4><b>The Percona plan</b></h4>
<p><span style="font-weight: 400">We intend to open reranking as well. Our initial target is </span><span style="font-weight: 400">BAAI/bge-reranker-large</span><span style="font-weight: 400">, a strong cross-encoder text-ranking model from the Beijing Academy of Artificial Intelligence, published on Hugging Face under the permissive MIT license.</span></p>
<h3><b> Contextual chunking and multimodal pipelines</b><a class="anchor-link" id="contextual-chunking-and-multimodal-pipelines"></a></h3>
<p><b>Contextual chunking</b><span style="font-weight: 400"> matters because embedding models have fixed context windows, so anything longer than a few paragraphs has to be split before it can be indexed. Split it naively on a character count, and you shred the meaning: a clause reading &ldquo;this must be renewed within 30 days&rdquo; is worthless when &ldquo;this&rdquo; was defined two chunks earlier. Contextual and late-chunking techniques embed each chunk with awareness of the surrounding document, so the retrieved passage still makes sense on its own. This is the difference between a RAG system that cites something useful and one that confidently quotes a fragment.</span></p>
<p><b>Multimodal pipelines</b><span style="font-weight: 400"> embed text and images into a single vector space, so a search for &ldquo;worn leather armchair, mid-century&rdquo; can match a photograph with no caption. Product catalogs, media archives, scanned paperwork, engineering diagrams &ndash; anywhere the information lives in the picture rather than the metadata.</span></p>
<h4><b>The Percona plan</b></h4>
<p><span style="font-weight: 400">For both of these, the path available today is a Voyage cloud API, called and paid for per token, with your content leaving your network. Meanwhile, the open-weights ecosystem offers excellent cross-encoder rerankers such as </span><a href="https://bge-model.com/bge/bge_m3.html"><span style="font-weight: 400">BGE-M3 from BAAI</span></a><span style="font-weight: 400">, and </span><a href="https://github.com/mehdidc/clip_rerank"><span style="font-weight: 400">CLIP-</span></a><span style="font-weight: 400"> and </span><a href="https://arxiv.org/pdf/2303.15343"><span style="font-weight: 400">SigLIP-class</span></a><span style="font-weight: 400"> multimodal encoders that run comfortably on a single GPU, or on CPU if you are patient. None of them is wired in yet. We would like to change that.</span></p>
<h3><b>Search-index backup, restore, and recovery</b><a class="anchor-link" id="search-index-backup-restore-and-recovery"></a></h3>
<p><span style="font-weight: 400">This is documented rather than absent, and it is worth reading closely to understand what it asks of you.</span></p>
<p><span style="font-weight: 400">mongot</span><span style="font-weight: 400"> is not your primary data store, so a lost index can always be rebuilt from </span><span style="font-weight: 400">mongod</span><span style="font-weight: 400">. The docs note the trade-off in the same breath: index builds &ldquo;can be slow and in some cases can take days to complete.&rdquo; For anything with a recovery-time objective, days of degraded search after a disk failure need a faster answer.</span></p>
<p><span style="font-weight: 400">That faster answer is a filesystem snapshot, and here is the whole procedure. Stop </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400">, then snapshot its data directory with the tool of your choice &ndash; the docs provide a working LVM example. To restore, put the directory back, generate a fresh server identity, and restart. </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400"> then resumes replication from </span><span style="font-weight: 400">mongod</span><span style="font-weight: 400"> and catches up.</span></p>
<p><span style="font-weight: 400">It works. It is also entirely yours to build, and there are a few properties worth planning around:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">No orchestration or scheduling, and no coordination with your database backup &ndash; so no consistent point-in-time across </span><span style="font-weight: 400">mongod</span><span style="font-weight: 400"> and </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">No object storage integration.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The search process is stopped while the copy is taken.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The snapshot has a shelf life. A </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400"> backup is valid only for as long as the change stream can carry it forward, so a snapshot older than your oplog retention window is detected as having fallen off the oplog and triggers the full rebuild you took the snapshot to avoid.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">On Kubernetes, MongoDB Controllers for Kubernetes does not back up or restore </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400"> volumes, and the docs recommend planning this with your storage platform.</span></li>
</ul>
<h4><b>The Percona plan</b></h4>
<p><span style="font-weight: 400">Automated, scheduled, verified backups are a problem the open-source community solved for databases a long time ago, and search indexes deserve the same treatment. Percona Backup for MongoDB and Percona Operator for MongoDB are a natural fit: PBM to orchestrate search-index snapshots alongside the database backup it already handles, with fast index initialization from object storage &ndash; S3, Azure, GCS, MinIO &ndash; instead of a full change-stream replay. The goal is for search-index recovery to be something you configure once, rather than script.</span></p>
<h3><b>Observability</b><a class="anchor-link" id="observability"></a></h3>
<p><span style="font-weight: 400">There is a </span><span style="font-weight: 400">/metrics</span><span style="font-weight: 400"> endpoint that exposes a great deal. What isn&rsquo;t there yet is anything built on top of it. Atlas has a Search Metrics UI; for self-managed deployments, dashboards are, in the documentation&rsquo;s own phrasing, &ldquo;not provided in a UI component.&rdquo; Alerting is yours to define, as are log retention and diagnostic-data rotation.</span></p>
<p><span style="font-weight: 400">To be concrete about what &ldquo;yours to define&rdquo; involves: the upstream docs publish a genuinely thoughtful set of seventeen recommended alerts across three severity tiers, each with example PromQL to adapt to your environment and thresholds to tune to your workload. The recommended approach is to implement the paging tier first, run it for a week, tune out false positives, then add the other two. That is good advice. It is also multi-week work, repeated for every deployment, before you have the monitoring that a hosted service provides on day one.</span></p>
<p><span style="font-weight: 400">Some of the behaviors worth alerting on are genuinely subtle. </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400"> enforces three disk thresholds internally, and the docs note they take effect whether or not you are monitoring:</span></p>
<ol>
<li><span style="font-weight: 400">Level 1: at 85% full, new index builds remain in </span><span style="font-weight: 400">PENDING.&nbsp;</span></li>
<li><span style="font-weight: 400">Level 2 &ndash; at 90%, steady-state replication is disabled &ndash; existing indexes stop receiving change events, and search begins serving stale results while the database itself reports healthy. </span></li>
<li><span style="font-weight: 400">Level 3: at 95%, the process stops and requires disk space to be freed before it restarts cleanly. The middle threshold is the one worth wiring up carefully, because it doesn&rsquo;t announce itself.</span></li>
</ol>
<p><a href="https://www.percona.com/wp-content/uploads/2026/08/pmm-mongot-dashboard.png"><img loading="lazy" decoding="async" class="aligncenter wp-image-51390 size-full" src="https://www.percona.com/wp-content/uploads/2026/08/pmm-mongot-dashboard.png" alt="" width="2285" height="1168"></a></p>
<h4><b>The Percona plan</b></h4>
<p><span style="font-weight: 400">Percona Monitoring and Management is where this belongs. We believe that collecting these metrics, presenting them on turnkey dashboards, and shipping alert rules with sensible thresholds is exactly the kind of work that should be done once and shared, rather than rebuilt by every team. Sync lag, heap and JVM health, index build progress, executor queue depth, disk headroom &ndash; including an alert for the case above, so you learn that replication stopped before your users do.</span></p>
<h2><span style="font-weight: 400">About the license</span><a class="anchor-link" id="about-the-license"></a></h2>
<p><a href="https://github.com/mongodb/mongot"><span style="font-weight: 400">mongot</span></a><span style="font-weight: 400"> is published under the Server Side Public License, and so is our distribution. You can read every line of it on </span><a href="https://github.com/percona/percona-mongot"><span style="font-weight: 400">GitHub</span></a><span style="font-weight: 400">, and we add no restrictions of our own on top.</span></p>
<p><span style="font-weight: 400">SSPL is source-available rather than OSI-approved open source, and we would rather say so than blur the term. What we can commit to is the part we control: </span></p>
<ul>
<li><span style="font-weight: 400">Capabilities stay yours. </span></li>
<li><span style="font-weight: 400">Self-hostable. </span></li>
<li><span style="font-weight: 400">Air-gappable. </span></li>
<li><span style="font-weight: 400">No metered API on the critical path. </span></li>
<li><span style="font-weight: 400">Software remains open and free.</span></li>
</ul>
<p><span style="font-weight: 400">Choice of automation, choice of model, choice of where the inference happens. That is the freedom we are working toward.</span></p>
<h2><span style="font-weight: 400">Getting started</span><a class="anchor-link" id="getting-started"></a></h2>
<p><span style="font-weight: 400">Percona Search for MongoDB requires Percona Server for MongoDB 8.3, which we shipped as a Technical Preview last week &ndash; the first Percona release carrying the </span><span style="font-weight: 400">$search</span><span style="font-weight: 400">, </span><span style="font-weight: 400">$searchMeta</span><span style="font-weight: 400">, </span><span style="font-weight: 400">$vectorSearch</span><span style="font-weight: 400">, </span><span style="font-weight: 400">$rankFusion</span><span style="font-weight: 400"> and </span><span style="font-weight: 400">$scoreFusion</span><span style="font-weight: 400"> stages that the search process plugs into.</span></p>
<ul>
<li style="font-weight: 400"><b>Packages:</b><span style="font-weight: 400"> grab Percona Search for MongoDB 1.70.3-1 from </span><a href="https://www.percona.com/downloads/"><span style="font-weight: 400">percona.com/downloads</span></a><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><b>Installation and configuration:</b><span style="font-weight: 400"> see the </span><a href="https://docs.percona.com/percona-search-for-mongodb/install-mongot.html"><span style="font-weight: 400">Percona Search for MongoDB documentation</span></a><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><b>On Kubernetes:</b><span style="font-weight: 400"> search is supported in </span><a href="https://docs.percona.com/percona-operator-for-mongodb/RN/Kubernetes-Operator-for-PSMONGODB-RN1.23.0.html"><span style="font-weight: 400">Percona Operator for MongoDB 1.23.0</span></a><span style="font-weight: 400">, released last week. Enable </span><span style="font-weight: 400">spec.search</span><span style="font-weight: 400"> &ndash; the </span><span style="font-weight: 400">enabled</span><span style="font-weight: 400"> flag, a </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400"> image, </span><span style="font-weight: 400">size: 1</span><span style="font-weight: 400">, and a sized PVC &ndash; and the Operator deploys the search process, wires up its authentication and internal TLS, and keeps the index in sync for both replica sets and sharded clusters, one </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400"> per shard. Details in the </span><a href="https://www.percona.com/blog/percona-operator-for-mongodb-1-23-0-clustersync-vector-search-pvc-snapshot-backups/"><span style="font-weight: 400">1.23.0 announcement</span></a><span style="font-weight: 400">.</span></li>
</ul>
<h2><span style="font-weight: 400">Before you deploy it</span><a class="anchor-link" id="before-you-deploy-it"></a></h2>
<p><span style="font-weight: 400">This is a Technical Preview. Please don&rsquo;t run it in production yet.</span></p>
<p><span style="font-weight: 400">Specifically, this version may not fully work with the rest of the Percona software for MongoDB:</span></p>
<ul>
<li style="font-weight: 400"><b>Percona Backup for MongoDB (PBM)</b><span style="font-weight: 400"> doesn&rsquo;t yet cover search indexes.</span></li>
<li style="font-weight: 400"><b>Percona Operator for MongoDB</b><span style="font-weight: 400"> search support, recently released in 1.23.0, is currently in tech preview and limited to 1 search node. More automation is coming in the next version.</span></li>
<li style="font-weight: 400"><b>Percona Monitoring and Management&nbsp;</b><span style="font-weight: 400">doesn&rsquo;t have search dashboards yet, but they&rsquo;re&nbsp;coming!</span></li>
</ul>
<p><span style="font-weight: 400">Point it at a copy of your data. Then share with us where it breaks for you.</span></p>
<h2><span style="font-weight: 400">Tell us what you need</span><a class="anchor-link" id="tell-us-what-you-need"></a></h2>
<p><span style="font-weight: 400">Everything above is a position, which means it can be wrong. If we&rsquo;ve missed a limitation, picked the wrong first target, or left out the embedding provider you actually use, we would like to hear it:</span></p>
<ul>
<li style="font-weight: 400"><b>Forum:</b> <a href="https://forums.percona.com/c/mongodb/24"><span style="font-weight: 400">forums.percona.com</span></a></li>
<li style="font-weight: 400"><b>Source:</b> <a href="https://github.com/percona/percona-mongot"><span style="font-weight: 400">github.com/percona/percona-mongot</span></a></li>
</ul>
<p><span style="font-weight: 400">Search and AI on your own data, on your own hardware, with the model you chose. That is what we are building, and that is what we mean by openness.</span></p>
<p><span style="font-weight: 400">If you&rsquo;re not using Percona for MongoDB yet but you&rsquo;re interested in Percona Search for MongoDB, you might like to read how </span><a href="https://www.percona.com/customer-story/sailthru/"><span style="font-weight: 400">Sailthru by Zeta cut more than $1 million a year</span></a><span style="font-weight: 400"> by migrating to Percona Server for MongoDB.</span></p>
<p><span style="font-weight: 400">The way is open.</span></p>
<p><i><span style="font-weight: 400">Disclaimer: Roadmap items are intentions, not delivery commitments. Scope and sequencing may change, and the fastest way to change them is to tell us what you need.</span></i></p>
<p>&nbsp;</p>
<p>The post <a href="https://www.percona.com/blog/the-open-way-of-percona-search-for-mongodb/">The open way of Percona Search for MongoDB</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p><a href="https://www.percona.com/blog/the-open-way-of-percona-search-for-mongodb/">The open way of Percona Search for MongoDB</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>The DuckDB MySQL engine at 500 GB</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/the-duckdb-mysql-engine-at-500-gb/" />
      <id>https://www.percona.com/blog/the-duckdb-mysql-engine-at-500-gb/</id>
      <updated>2026-08-07T12:09:29+00:00</updated>
      <author><name>Evgeniy Patlan</name></author>
      <summary type="html"><![CDATA[<p>We ran DuckDB MySQL storage engine at scale factor 500. It is around 500 GB of raw TPC-H, three billion lineitem rows  on an 80-core server with 187 GB of RAM. Three engines on the same box: InnoDB, our MySQL+DuckDB engine, and plain DuckDB as the reference. Here is what came out. InnoDB finished 18 … Continued<br />
The post The DuckDB MySQL engine at 500 GB appeared first on Percona.</p>
<p><a href="https://www.percona.com/blog/the-duckdb-mysql-engine-at-500-gb/">The DuckDB MySQL engine at 500 GB</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">We ran DuckDB MySQL storage engine at scale factor 500. It is around 500 GB of raw TPC-H, three billion </span><span style="font-weight: 400">lineitem</span><span style="font-weight: 400"> rows&nbsp; on an 80-core server with 187 GB of RAM. Three engines on the same box: InnoDB, our MySQL+DuckDB engine, and plain DuckDB as the reference.</span></p>
<p><span style="font-weight: 400">Here is what came out. InnoDB finished 18 of the 22 queries and spent more than 28 hours of query time on them. Four never finished. Our engine ran all 22 in about three minutes. It loaded the data 25 times faster than InnoDB, and it used 5 times less disk. On the queries it stays close to plain DuckDB, and on a few it is ahead.</span></p>
<p><span style="font-weight: 400">It&rsquo;s still an experiment, not production software. Code and the benchmark harness are on GitHub under GPLv2: </span><a href="https://github.com/Percona-Lab/ducksdb-mysql-engine"><span style="font-weight: 400">https://github.com/Percona-Lab/ducksdb-mysql-engine</span></a><span style="font-weight: 400">.</span></p>
<h2><span style="font-weight: 400">The machine, and how we ran it</span><a class="anchor-link" id="the-machine-and-how-we-ran-it"></a></h2>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">One server, 80 cores, 187.5 GB RAM.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">SF500: about 500 GB of raw CSV, 3,000,028,242 </span><span style="font-weight: 400">lineitem</span><span style="font-weight: 400"> rows.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Three engines, one at a time: InnoDB, our engine, native DuckDB.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">All of it through the harness in the repo (</span><span style="font-weight: 400">bench/tb</span><span style="font-weight: 400">), in Docker.</span></li>
</ul>
<p><span style="font-weight: 400">Two details about how we ran it change how the numbers read.</span></p>
<p><span style="font-weight: 400">The load streams. We generate a chunk of CSV, load it, delete it, then generate the next one. So the disk never holds more than one 20 GB chunk, which is the only reason 500 GB fits on the box at all.</span></p>
<p><span style="font-weight: 400">And &ldquo;native DuckDB&rdquo; is not a second copy of the data. It opens the engine&rsquo;s own DuckDB file read-only and queries that. Same bytes on both sides. That keeps the comparison honest, and it means there is no separate native load time to report.</span></p>
<h2><span style="font-weight: 400">Loading the data</span><a class="anchor-link" id="loading-the-data"></a></h2>
<table style="height: 106px" width="438">
<thead>
<tr>
<th><span style="font-weight: 400">Engine</span></th>
<th><span style="font-weight: 400">Load time</span></th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="font-weight: 400">ENGINE=DuckDB (COPY fast path)</span></td>
<td><span style="font-weight: 400">36m 05s</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">InnoDB (bulk LOAD DATA)</span></td>
<td><span style="font-weight: 400">15h 21m</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">InnoDB took 25.5 times longer. The engine hands </span><span style="font-weight: 400">LOAD DATA</span><span style="font-weight: 400"> straight to a DuckDB </span><span style="font-weight: 400">COPY</span><span style="font-weight: 400"> instead of going row by row through the handler, so the three billion </span><span style="font-weight: 400">lineitem</span><span style="font-weight: 400"> rows go in in about nineteen minutes, and the whole set in thirty-six. InnoDB inserts row by row and builds the primary key as it goes. That is where the rest of the fifteen hours goes.</span></p>
<h2><span style="font-weight: 400">Storage on disk</span><a class="anchor-link" id="storage-on-disk"></a></h2>
<table style="height: 161px" width="581">
<thead>
<tr>
<th><span style="font-weight: 400">Component</span></th>
<th><span style="font-weight: 400">Size</span></th>
<th><span style="font-weight: 400">vs raw CSV</span></th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="font-weight: 400">raw TPC-H CSV</span></td>
<td><span style="font-weight: 400">500.0 GB</span></td>
<td><span style="font-weight: 400">100%</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">ENGINE=DuckDB (</span><span style="font-weight: 400">tpch.duckdb</span><span style="font-weight: 400">)</span></td>
<td><span style="font-weight: 400">132.4 GB</span></td>
<td><span style="font-weight: 400">26% (3.78x smaller)</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">InnoDB (</span><span style="font-weight: 400">tpch/*.ibd</span><span style="font-weight: 400">)</span></td>
<td><span style="font-weight: 400">673.2 GB</span></td>
<td><span style="font-weight: 400">135%</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">DuckDB stores columns and compresses them, so 500 GB of CSV comes down to 132 GB. InnoDB stores rows and carries the index with them, and it ends up bigger than the CSV it came from: 673 GB, five times the DuckDB file. The InnoDB </span><span style="font-weight: 400">lineitem.ibd</span><span style="font-weight: 400"> on its own is 446 GB. That is more than three times our entire database.</span></p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-51356 size-full" src="https://www.percona.com/wp-content/uploads/2026/08/chart-storage_fix.png" alt="" width="1186" height="659"></p>
<p style="text-align: center"><i><span style="font-weight: 400">Storage, lower is better. The DuckDB engine holds all of SF500 in 132 GB.</span></i></p>
<h2><span style="font-weight: 400">Query time</span><a class="anchor-link" id="query-time"></a></h2>
<p><span style="font-weight: 400">All 22 queries. Warm runs, minimum of a few, in seconds. InnoDB had a two-hour cap per query; the ones that hit it are marked DNF.</span><span style="font-weight: 400"><br>
</span></p>
<p>&nbsp;</p>
<table style="height: 286px" width="620">
<thead>
<tr>
<th><span style="font-weight: 400">Query</span></th>
<th><span style="font-weight: 400">InnoDB</span></th>
<th><span style="font-weight: 400">MySQL+DuckDB (ours)</span></th>
<th><span style="font-weight: 400">native DuckDB</span></th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="font-weight: 400">Q1</span></td>
<td><span style="font-weight: 400">11864.5</span></td>
<td><span style="font-weight: 400">11.1</span></td>
<td><span style="font-weight: 400">5.2</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Q6</span></td>
<td><span style="font-weight: 400">3539.4</span></td>
<td><span style="font-weight: 400">1.3</span></td>
<td><span style="font-weight: 400">4.1</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Q9</span></td>
<td><span style="font-weight: 400">DNF</span></td>
<td><span style="font-weight: 400">17.1</span></td>
<td><span style="font-weight: 400">18.1</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Q13</span></td>
<td><span style="font-weight: 400">DNF</span></td>
<td><span style="font-weight: 400">17.1</span></td>
<td><span style="font-weight: 400">10.4</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Q18</span></td>
<td><span style="font-weight: 400">3846.1</span></td>
<td><span style="font-weight: 400">27.0</span></td>
<td><span style="font-weight: 400">11.9</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Q19</span></td>
<td><span style="font-weight: 400">6672.3</span></td>
<td><span style="font-weight: 400">2.4</span></td>
<td><span style="font-weight: 400">8.6</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Q21</span></td>
<td><span style="font-weight: 400">14211.7</span></td>
<td><span style="font-weight: 400">26.0</span></td>
<td><span style="font-weight: 400">15.1</span></td>
</tr>
<tr>
<td><b>All 22</b></td>
<td><b>18/22 finished, ~28 h</b></td>
<td><b>185.6 s</b></td>
<td><b>152.7 s</b></td>
</tr>
</tbody>
</table>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-51359 size-full" src="https://www.percona.com/wp-content/uploads/2026/08/chart-query-times.png" alt="" width="2384" height="960"></p>
<p><i><span style="font-weight: 400">SF500, all 22 queries, log scale, lower is better. Hatched InnoDB bars did not finish inside the cap.</span></i></p>
<p><span style="font-weight: 400">Two things to take from this.</span></p>
<p><span style="font-weight: 400">InnoDB is far behind, which is no surprise. Scanning three billion rows for a wide </span><span style="font-weight: 400">GROUP BY</span><span style="font-weight: 400"> or a six-way join is the wrong job for a row store. Four queries (Q9, Q13, Q17, Q20) did not finish at all, and the eighteen that did add up to more than 28 hours. This is the exact problem the engine is for. It is not a mark against InnoDB, which is doing the transactional job it was built for.</span></p>
<p><span style="font-weight: 400">The comparison worth reading is our engine against plain DuckDB, since both are the same DuckDB reading the same file. Over all 22 they are close: 186 seconds for ours, 153 for native. Query by query it goes both ways. On the selective ones ours is often faster &mdash; Q6 (1.3 vs 4.1), Q19 (2.4 vs 8.6), Q17, Q20. On the biggest joins native wins &mdash; Q18 (27 vs 12), Q21, Q1. That gap comes from settings, not data: the memory limit, the thread count, and running inside </span><span style="font-weight: 400">mysqld</span><span style="font-weight: 400"> versus a bare CLI. Either way, both are around a thousand times faster than the row store.</span></p>
<h2><span style="font-weight: 400">Correctness</span><a class="anchor-link" id="correctness"></a></h2>
<p><span style="font-weight: 400">We checked the answers, not only the clock. For every query we compared our engine&rsquo;s output to native DuckDB&rsquo;s, numbers rounded to four decimals and the order ignored. 21 of 22 matched exactly. None mismatched. One was skipped because a result file came back empty on one side. So the engine gives the same answers as plain DuckDB.</span></p>
<h2><span style="font-weight: 400">What this means, and where it stops</span><a class="anchor-link" id="what-this-means-and-where-it-stops"></a></h2>
<p><span style="font-weight: 400">At 500 GB the small-scale picture holds and gets sharper. Analytical queries that took hours on InnoDB, or never finished, come back in seconds on the DuckDB engine. The load is far quicker, and the footprint is far smaller. All of it inside one MySQL server, with the tables queried the normal way.</span></p>
<p><span style="font-weight: 400">The limits are the same as before:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">It is for analytics, not OLTP. Point lookups and single-row work stay on the row path, where an index seek is the right tool.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">DuckDB runs inside </span><span style="font-weight: 400">mysqld</span><span style="font-weight: 400">, so a heavy query under a tight memory limit can go over budget. </span><span style="font-weight: 400">DUCKSDB_MEMORY_LIMIT</span><span style="font-weight: 400"> and </span><span style="font-weight: 400">DUCKSDB_TEMP_DIR</span><span style="font-weight: 400"> let it spill to disk instead of failing. We set a limit here so the big CTEs spill rather than get OOM-killed.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Some queries still fall back to normal MySQL and run on the row path.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">It is one workload on one machine. The result is strong, but the engine is still an experiment, not something for production traffic.</span></li>
</ul>
<h2><span style="font-weight: 400">Try it</span><a class="anchor-link" id="try-it"></a></h2>
<p><span style="font-weight: 400">Pull the image and run your own queries:</span></p>
<p><span style="font-weight: 400">docker run </span><span style="font-weight: 400">-d</span> <span style="font-weight: 400">-p</span><span style="font-weight: 400"> 3306:3306 </span><span style="font-weight: 400">-e</span><span style="font-weight: 400"> MYSQL_ROOT_PASSWORD=secret </span><span style="font-weight: 400"></span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400">&nbsp; perconalab/ducksdb-mysql-engine:latest</span></p>
<p><span style="font-weight: 400">The engine, the patches, and the harness that produced these numbers are on GitHub: </span><a href="https://github.com/Percona-Lab/ducksdb-mysql-engine"><span style="font-weight: 400">https://github.com/Percona-Lab/ducksdb-mysql-engine</span></a><span style="font-weight: 400">. The per-query numbers and the method are in the repo. If it breaks, or your hardware gives different numbers, open an issue.</span></p>
<p>The post <a href="https://www.percona.com/blog/the-duckdb-mysql-engine-at-500-gb/">The DuckDB MySQL engine at 500 GB</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p><a href="https://www.percona.com/blog/the-duckdb-mysql-engine-at-500-gb/">The DuckDB MySQL engine at 500 GB</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Herding Goats Across Continents: How We Took 100 People From Around the World to Turkey for an Offsite (and Lived to Tell the Tale)</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/08/06/herding-goats-percona-turkey-offsite/" />
      <id>https://percona.community/blog/2026/08/06/herding-goats-percona-turkey-offsite/</id>
      <updated>2026-08-06T11:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>Every great journey needs a guide.</p>
<p><a href="https://percona.community/blog/2026/08/06/herding-goats-percona-turkey-offsite/">Herding Goats Across Continents: How We Took 100 People From Around the World to Turkey for an Offsite (and Lived to Tell the Tale)</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Every great journey needs a guide.</p>
<p>Ours just happened to be a mountain goat.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/08/turkey-goat-lego.jpg" alt="LEGO mountain goat with Percona branding on a laptop"></figure>
</p>
<p>Fresh off a brand-new Percona branding reveal &mdash; complete with a rugged mountain goat mascot, a custom-made goat cake, and even a (surprisingly inspirational) LEGO mountain goat &mdash; we set out to bring our global Software Engineering team together in Turkey.</p>
<p>And while planning an offsite for a fully remote, globally distributed team sounds simple enough&hellip; just wait until you try to do it.</p>
<p>We&rsquo;re talking about 100 people, 30+ countries, coordinating over 200,000 miles of travel, multiple time zones, passports, dietary restrictions, flight delays, and the occasional &ldquo;wait, what do you mean my passport expires in three months?&rdquo; <em>(you know who you are)</em> moment. Suddenly, you&rsquo;re not just planning a trip &mdash; you&rsquo;re orchestrating a small traveling universe.</p>
<p>Welcome to the step-by-step story of how we brought a global Percona team together in Turkey, and what it <em>really</em> takes to make something like this happen.</p>
<h2 id="step-one-accept-that-details-are-your-new-personality">Step One: Accept That Details Are Your New Personality<a class="anchor-link" id="step-one-accept-that-details-are-your-new-personality"></a></h2>
<p>When planning an offsite of this scale, you quickly learn one thing: there is no such thing as being &ldquo;too detailed.&rdquo;</p>
<p>There is also no such thing as too much information, or having too much collaboration, when it comes to working through the countless moving pieces.</p>
<p>Success at an event like this doesn&rsquo;t come from starting from scratch. It comes from leaning heavily on the people who have done it before. Their experiences, lessons learned, and even their past mistakes are incredibly valuable.</p>
<p>We needed to actively seek out those insights, ask questions, and build on what already existed rather than reinventing the wheel. If previous events weren&rsquo;t successful, you probably wouldn&rsquo;t be doing it again. Lean into previous experiences and successes.</p>
<p>At the same time, it&rsquo;s <strong>not just about repeating what&rsquo;s been done &mdash; it&rsquo;s about refining it.</strong> Each offsite should be a better, more thoughtful version of the last. That means taking previous experiences, identifying what worked and what didn&rsquo;t, and making deliberate improvements across logistics, communication, and attendee experience.</p>
<p>None of this happens last minute. To truly manage expectations and execute smoothly, planning conversations need to start early &mdash; ideally 6&ndash;8 months in advance <em>(even though the Percona Turkey retreat was accomplished in just four months)</em>. That runway gives teams the time to align on goals, pressure-test ideas, collaborate across functions, and work through the inevitable complexities that come with an event of this scale.</p>
<p>Success lies in thinking through <em>every single touchpoint</em> of the attendee experience. From the moment attendees board their flight, train, car, or motorcycle to the final farewell high-five, it&rsquo;s an experience.</p>
<p>Paying close attention to each touchpoint shows up in many moments, including:</p>
<ul>
<li>Branded coasters placed on every table setting during business meetings (yes, people notice)</li>
<li>Signage throughout the venue so no one ends up in a yoga class instead of a database workshop</li>
<li>Check-in gifts that reinforce the Percona brand (and maybe include a surprise or two&hellip; goat-related, naturally)</li>
<li>Carefully planned cocktail hours where &ldquo;networking&rdquo; magically turns into genuine connection</li>
<li>Group dinners where teammates finally meet the humans behind the Slack avatars</li>
<li>A candy swap featuring treats from every country represented &mdash; it doesn&rsquo;t need to be formal. Everyone piles their local candies on a table for the masses</li>
<li>A myriad of team-building activities spanning four days, including a competition to build the tallest free-standing printer-paper tower using only paper and tape <em>(sidenote: be prepared for every single team to think that they won and the other teams cheated)</em></li>
</ul>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/08/turkey-paper-tower.jpg" alt="Team building a paper tower at the Percona Turkey offsite"></figure>
</p>
<p>But what people remember most isn&rsquo;t just the agenda, t-shirts, stickers, goat swag, candy, or coffee breaks. It&rsquo;s the combination of these thoughtful touches woven into every moment.</p>
<p>It&rsquo;s not just logistics that make the trip &mdash; it&rsquo;s using those details and that collaboration to master storytelling.</p>
<h2 id="step-two-time-may-not-be-on-your-side-but-deadlines-and-effective-communication-are">Step Two: Time May Not Be on Your Side (But Deadlines and Effective Communication Are)<a class="anchor-link" id="step-two-time-may-not-be-on-your-side-but-deadlines-and-effective-communication-are"></a></h2>
<p>A trip like this doesn&rsquo;t come together overnight. But sometimes it needs to come together pretty close to overnight. That means a very well thought-out internal communications plan and schedule that answers as many questions as possible <em>before</em> they are even asked.</p>
<p>But no matter how solid a formal communications plan is, there will be moments when questions get asked that were already addressed &mdash; which is why finding other ways to communicate, collaborate, and involve stakeholders and attendees is <em>crucial</em>. Think dedicated Slack channels with full transparency and readily available information, weekly touchpoints with department heads who can convey updates and reminders to their teams, and frequent conversations with finance, human resources, and legal throughout.</p>
<p>Behind the scenes, you&rsquo;re looking at <strong>100&ndash;150 hours of internal planning</strong> &mdash; all before the first suitcase is packed.</p>
<p>With that level of complexity, time becomes both a constraint and a forcing function. Deadlines matter, timelines matter, and how you communicate along the way matters even more. Clear, consistent communication is what keeps everything moving forward when there are dozens of parallel workstreams and stakeholders involved.</p>
<p>Internally, communication has to be frequent, detailed, and often repetitive. There&rsquo;s no room for assumptions or &ldquo;I thought someone else had that covered.&rdquo; Every update, decision, dependency, and small detail needs to be shared openly and documented so nothing slips through the cracks.</p>
<p>The more visibility the team has, the more aligned everyone stays.</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/08/turkey-dinner-toast.jpg" alt="Colleagues toasting at dinner during the Turkey offsite"></figure>
</p>
<p>That means being proactive, not reactive. Attendees need consistent touchpoints leading up to the trip: detailed itineraries, travel guidance, visa reminders, packing expectations, local logistics, and clear points of contact. Information should be centralized, easy to access, and reinforced multiple times so no one is left guessing.</p>
<p>During the event, communication doesn&rsquo;t stall. In fact, it accelerates &mdash; with adaptability, pivots, and clear, concise explanations.</p>
<p>Real-time updates, schedule reminders, transportation details, and contingency plans all need to be communicated clearly and quickly. When done right, attendees feel taken care of, confident, and fully present in the experience rather than worrying about logistics.</p>
<p>At the end of the day, strong communication &mdash; both internally and externally &mdash; is what turns a complex, global operation into something that feels seamless. It&rsquo;s not just about sharing information; it&rsquo;s about creating clarity, building trust, and ensuring every single person knows exactly where they need to be and when.</p>
<p>This also means over-communicating by design. Weekly check-ins turn into twice-weekly syncs as the event gets closer. Quick updates become detailed run-of-show documents. Slack threads involving all attendees, shared docs, and status trackers become the backbone of execution. It may feel like a lot, but that level of transparency is what prevents last-minute surprises.</p>
<p>Oh yeah &mdash; then there&rsquo;s vendor coordination and communication.</p>
<p>Hotels. Transportation. AV teams. Catering. Swag production. Signage. Shipping. Local experiences. Backup plans for your backup plans.</p>
<p>Each one comes with:</p>
<ul>
<li>Deadlines</li>
<li>Dependencies</li>
<li>Payment schedules</li>
<li>And the occasional &ldquo;we need final numbers by tomorrow or your menu disappears&rdquo; situation</li>
</ul>
<p>Miss a deadline? That&rsquo;s not just a small hiccup &mdash; it can mean:</p>
<ul>
<li>Increased costs</li>
<li>Limited availability</li>
<li>Or a last-minute scramble that no one wants to experience</li>
</ul>
<p><strong>Precision, effective communication, and timing aren&rsquo;t optional &mdash; they&rsquo;re everything.</strong></p>
<h2 id="step-three-build-the-experience-not-just-the-agenda">Step Three: Build the Experience, Not Just the Agenda<a class="anchor-link" id="step-three-build-the-experience-not-just-the-agenda"></a></h2>
<p>You can have the best presentations in the world, but that&rsquo;s not what people will remember.</p>
<p>What they <em>will</em> remember &mdash; and what they will certainly talk about afterwards:</p>
<ul>
<li>The first time they met a teammate in person after years of working together online</li>
<li>The laughs over dinner</li>
<li>The spontaneous conversations during coffee breaks</li>
<li>The shared &ldquo;we made it here&rdquo; energy</li>
</ul>
<p>For a company like Percona &mdash; <strong>100% remote, spanning 50+ countries</strong> &mdash; these experiences and moments aren&rsquo;t just nice-to-haves. They&rsquo;re essential.</p>
<p>An offsite like this creates:</p>
<ul>
<li>Stronger team bonding across regions and roles</li>
<li>Real human connections that make collaboration smoother</li>
<li>Cross-functional understanding that Slack threads simply can&rsquo;t replicate</li>
<li>A sense of belonging that transcends time zones</li>
</ul>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/08/turkey-card-game.jpg" alt="Colleagues playing cards together at the Turkey offsite"></figure>
</p>
<p>In short: it turns coworkers into teammates. As a remote-only company, Percona has a uniqueness with nearly 14% of employees who have been with the company for 10+ years. This isn&rsquo;t an accident &mdash; it&rsquo;s a direct result of the kind of networking, partnership, and friendships that these types of retreats create.</p>
<p>The agenda for an offsite retreat should be intentionally designed to educate, inspire, and elevate the overall experience &mdash; not just fill time on a schedule. Every presentation needs to be thoughtfully curated to add real value, ensuring it contributes meaningfully rather than feeling like content for content&rsquo;s sake.</p>
<h2 id="step-four-expect-the-unexpected-and-pack-snacks">Step Four: Expect the Unexpected (and Pack Snacks)<a class="anchor-link" id="step-four-expect-the-unexpected-and-pack-snacks"></a></h2>
<p>No matter how well you plan, something will go sideways.</p>
<p>Flights will be delayed. Luggage will take a scenic tour of another country. Someone will forget something important (realistically, multiple someones).</p>
<p>The key is not avoiding problems, but trusting yourself, your team, and your planning to be ready for them.</p>
<p>A few survival tips:</p>
<ul>
<li><strong>Always have contingency plans (plural). Seriously. Always. And seriously &mdash; plural.</strong> Hurricane hitting the beach on closing outdoor dinner night? Have a space ready to go and an action plan for communicating the pivot. All your swag held up in customs? Find the local flea market and craft shops and make your gifts local. Speaker forgot a slide advancer <em>and</em> the venue&rsquo;s failed <em>and</em> the venue&rsquo;s backup failed? Keep one on the ready with you at all times.</li>
<li><strong>Over-communicate everything.</strong> You can&rsquo;t tell people important things too many times.</li>
<li><strong>Build buffer time into the schedule.</strong> Imagine trying to get your three-year-old out the door early in the morning. It&rsquo;s like that, but with 100 adults. Give yourself some cushions.</li>
<li><strong>Keep a sense of humor handy</strong> &mdash; it&rsquo;s arguably your most valuable tool. You can diffuse a lot of tension with a little humorous quip. Humor brings the tension down, eases the panic, and grounds everyone. Even the most panic-stricken, reactionary executive you&rsquo;ve ever met enjoys a good laugh in tense moments.</li>
<li><strong>Slow down</strong> &mdash; panic happens when speed outmaneuvers thought. It&rsquo;s not a race, and you don&rsquo;t have to beat everyone to a solution. Trust yourself and trust your team, and slow down enough to think through clearly without the heaviness of undue pressure.</li>
</ul>
<h2 id="step-five-remember-why-youre-doing-it">Step Five: Remember Why You&rsquo;re Doing It<a class="anchor-link" id="step-five-remember-why-youre-doing-it"></a></h2>
<p>After the spreadsheets, the emails, the vendor calls, and the 47th revision of the rooming list, it&rsquo;s easy to forget the bigger picture.</p>
<p>But then the event starts.</p>
<p>People arrive. Conversations spark. Teams connect. Ideas flow. Energy builds.</p>
<p>And suddenly, all 100&ndash;150 hours (and then some) make perfect sense.</p>
<p>What you&rsquo;ve created isn&rsquo;t just an offsite &mdash; it&rsquo;s an experience that strengthens your team in ways no virtual meeting ever could.</p>
<p>Don&rsquo;t lose sight of your objective.</p>
<h2 id="final-thoughts-keep-climbing">Final Thoughts: Keep Climbing<a class="anchor-link" id="final-thoughts-keep-climbing"></a></h2>
<p>Bringing 100 people across the world to Turkey wasn&rsquo;t easy &mdash; but it was worth every detail, every deadline, and every late-night planning session.</p>
<p>When you return home from executing a trip like this, you will want to shut off all notifications and take a few days to yourself without having to resolve anything. But also make sure you ponder what you would have liked to see done differently. Would arranging transfers from the airport to the resort be more cost-effective and smooth than relying on taxis? Most likely. Would building in more leisure time and opportunities for attendees to explore the local surroundings be worth sacrificing one or two sessions of training? Absolutely.</p>
<p>Learn from that and adapt for next time.</p>
<p>Because at the end of the day, investing in your people &mdash; especially in a remote-first world &mdash; is one of the most impactful things you can do.</p>
<p>And if that investment comes with a mountain goat mascot, a LEGO companion, custom cakes, coasters, t-shirts, drinks, food, and out-of-shape pickup basketball games, then you will find a team that fully leans into the journey and the message.</p>
<p><figure><img decoding="async" src="https://percona.community/blog/2026/08/turkey-road-trip.jpg" alt="Two Perconians on a post-retreat road trip in Turkey"></figure>
</p>
<p>We came as individuals from across the globe.</p>
<p>On the day the retreat ended, I found myself in a rental car with a colleague I&rsquo;d never met in person before. Two co-workers from different sides of the globe, different cultures, and two very different paths that led us to that spot. We drove for a full day, exploring site after site and chatting and laughing about life, work, and music.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/08/turkey-team-photo.jpg" alt="The Software Engineering team at the Percona Turkey offsite"></figure>
</p>
<p>This interaction and moment doesn&rsquo;t happen without retreats like this.</p>
<p>We came together as members of the Software Engineering group at a place we all worked, and left as a unified team of Perconians &mdash; and as friends.</p>
<p><strong>That&rsquo;s your <em>real</em> ROI.</strong></p>

<p><a href="https://percona.community/blog/2026/08/06/herding-goats-percona-turkey-offsite/">Herding Goats Across Continents: How We Took 100 People From Around the World to Turkey for an Offsite (and Lived to Tell the Tale)</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>ClusterControl 2.5.0 brings ClickHouse support to on-prem, cloud and hybrid environments</title>
      <link rel="alternate" type="text/html" href="https://severalnines.com/blog/clustercontrol-2-5-0-brings-clickhouse-support-to-on-prem-cloud-and-hybrid-environments/" />
      <id>https://severalnines.com/blog/clustercontrol-2-5-0-brings-clickhouse-support-to-on-prem-cloud-and-hybrid-environments/</id>
      <updated>2026-08-05T08:38:18+00:00</updated>
      <author><name>Kyle Buzzell</name></author>
      <summary type="html"><![CDATA[<p>ClusterControl 2.5.0 is here, and it marks a milestone for the platform: ClickHouse joins the family of supported database engines — and with it, a capability no analytics vendor’s cloud can offer you. Wherever you run ClusterControl — on-premises, in any cloud, or hybrid — you can now deploy ClickHouse either as a standalone OLAP […]<br />
The post ClusterControl 2.5.0 brings ClickHouse support to on-prem, cloud and hybrid environments appeared first on Severalnines.</p>
<p><a href="https://severalnines.com/blog/clustercontrol-2-5-0-brings-clickhouse-support-to-on-prem-cloud-and-hybrid-environments/">ClusterControl 2.5.0 brings ClickHouse support to on-prem, cloud and hybrid environments</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><a href="https://severalnines.com/clustercontrol">ClusterControl</a> 2.5.0 is here, and it marks a milestone for the platform: <strong>ClickHouse joins the family of supported database engines</strong> &mdash; and with it, a capability no analytics vendor&rsquo;s cloud can offer you.</p>
<p>Wherever you run ClusterControl &mdash; on-premises, in any cloud, or hybrid &mdash; you can now deploy <a href="https://severalnines.com/clustercontrol/databases/clickhouse">ClickHouse</a> either as a <strong>standalone OLAP workload</strong> or as the <strong>analytics tier of a comprehensive database stack</strong>, operated alongside your transactional databases under a single control plane. Your environment, your infrastructure, your data.</p>
<p>This release also delivers a major upgrade to PostgreSQL backup workflows, introduces built-in usage metering for consumption-based operations, and brings back a user-favorite capability from ClusterControl v1. Let&rsquo;s dig in</p>
<h2 class="wp-block-heading" id="h-clickhouse-analytics-on-your-terms-in-any-environment">ClickHouse: analytics on your terms, in any environment<a class="anchor-link" id="clickhouse-analytics-on-your-terms-in-any-environment"></a></h2>
<p>Analytical workloads aren&rsquo;t nice-to-have anymore. Whether it&rsquo;s real-time dashboards, log analytics, or feeding features to AI systems, the OLAP tier is becoming as operationally critical as the transactional tier &mdash; and it deserves the same automation, monitoring, and sovereignty guarantees. ClickHouse support means you can run that tier on your own infrastructure, under your own control, with full lifecycle automation.</p>
<p>But running ClickHouse yourself has meant either adopting a vendor&rsquo;s cloud &mdash; with your analytical data leaving your environment &mdash; or hand-rolling deployment, monitoring, and operations. ClusterControl 2.5.0 gives you a third option: <strong>full lifecycle automation for ClickHouse on infrastructure you control</strong>, whether that&rsquo;s a single analytics node or the OLAP tier of your entire database estate.</p>
<p>With 2.5.0 you can:</p>
<ul class="wp-block-list">
<li><strong>Deploy automatically</strong> &mdash; single-node instances or replicated clusters with embedded Keeper, provisioned through the same workflow you already use for MySQL, PostgreSQL, MongoDB, and Redis</li>
<li><strong>Monitor and alert</strong> through ClusterControl&rsquo;s unified dashboards &mdash; one pane of glass across your transactional and analytical estate</li>
<li><strong>Back up and restore</strong> your ClickHouse clusters</li>
<li><strong>Scale</strong> as analytical workloads grow</li>
<li><strong>Import existing ClickHouse clusters</strong> <strong>into </strong>ClusterControl management, and drive operations from the s9s CLI and API as well as the UI </li>
<li><strong>Secure inter-node communication &mdash;</strong> SSL-encrypted links between cluster nodes with per-node certificates</li>
</ul>
<p>Run it standalone if analytics is all you need. Or run it as one tier of a comprehensive stack &mdash; ClickHouse for OLAP next to MySQL, PostgreSQL, MongoDB, and Redis for OLTP, with load balancers, backups, and access control managed the same way across all of them. Same workflow, same alerting, same operational muscle memory, in whatever environment your requirements dictate.</p>
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="603" src="https://severalnines.com/wp-content/uploads/2026/08/clustercontrol-clickhouse-keeper-cluster-topology-1024x603.png" alt="" class="wp-image-44339"></figure>
<h3 class="wp-block-heading" id="h-what-s-next-for-clickhouse-in-clustercontrol">What&rsquo;s next for ClickHouse in ClusterControl<a class="anchor-link" id="whats-next-for-clickhouse-in-clustercontrol"></a></h3>
<p>This release is the foundation, and the roadmap builds directly on it. Planned improvements include:</p>
<ul class="wp-block-list">
<li><strong>Replication from MySQL and PostgreSQL into ClickHouse</strong> &mdash; feed your analytics tier directly from your operational databases, targeted for the next release</li>
<li><strong>Sharded ClickHouse cluster deployments</strong> for horizontally scaled analytical workloads</li>
<li><strong>ClickHouse user and role management</strong> from the UI</li>
</ul>
<h2 class="wp-block-heading" id="h-postgresql-backups">PostgreSQL backups<a class="anchor-link" id="postgresql-backups"></a></h2>
<p>Two significant improvements land for <a href="https://severalnines.com/clustercontrol/databases/postgresql">PostgreSQL</a> in 2.5.0:</p>
<p><strong>Streaming backups directly to S3.</strong> ClusterControl now streams pg_basebackup output straight to S3-compatible object storage &mdash; Amazon S3, MinIO, Google Cloud Storage (S3 mode), DigitalOcean Spaces, Wasabi, and others. No local disk staging, no oversized temp volumes on your database hosts, and a shorter backup-to-cloud pipeline overall.</p>
<p><strong>Native incremental backups (PostgreSQL 17+).</strong> PostgreSQL 17 introduced incremental backup support in pg_basebackup, and ClusterControl 2.5.0 puts it to work &mdash; including S3 upload. For large databases, that means dramatically smaller and faster backups between full baselines.</p>
<figure class="wp-block-image size-full"><img decoding="async" src="https://severalnines.com/wp-content/uploads/2026/07/image2.png" alt="Postgres GUI backup wizard showing streaming and incremental backup improvements in ClusterControl v2.5.0" class="wp-image-44331"></figure>
<h2 class="wp-block-heading">Usage metering and operator billing (Pay-As-You-Go)<a class="anchor-link" id="usage-metering-and-operator-billing-pay-as-you-go"></a></h2>
<p>For operators, MSPs, and platform teams running database services on a consumption basis, 2.5.0 introduces built-in <strong>usage metering</strong>:</p>
<ul class="wp-block-list">
<li>Hourly usage snapshots collected per controller across your managed estate</li>
<li>On-demand billing reports &mdash; estate-wide or filtered by tag or cluster &mdash; with <strong>cryptographic sealing</strong> and independent verification</li>
<li>A dedicated <strong>operator billing page</strong> in the multi-controller UI, with JSON/CSV export</li>
</ul>
<p>It&rsquo;s the foundation for Pay-As-You-Go commercial models on infrastructure you control &mdash; a natural fit for Sovereign DBaaS operations. The feature is off by default and only surfaced when metering is enabled.</p>
<h2 class="wp-block-heading">Cluster-wide configuration management is back<a class="anchor-link" id="cluster-wide-configuration-management-is-back"></a></h2>
<p>By popular demand from ClusterControl v1: change a database parameter across <strong>every node in a MySQL cluster in a single action</strong>. Dynamic parameters are applied at runtime &mdash; no per-node edit-and-restart cycle, and less downtime for routine configuration changes in production.</p>
<h2 class="wp-block-heading" id="h-postgresql-database-user-management">PostgreSQL database user management<a class="anchor-link" id="postgresql-database-user-management"></a></h2>
<p>reate and manage users and roles from the UI, with fine-grained privileges down to schema and table level, lock/disable/enable lifecycle operations, pg_hba.conf editing with tracked changes, and user search and filtering</p>
<h2 class="wp-block-heading">Other noteworthy improvements<a class="anchor-link" id="other-noteworthy-improvements"></a></h2>
<ul class="wp-block-list">
<li><strong>Scalable controllers pool hardening</strong> &mdash; UI-driven upgrades of remote pool members, automatic alarms on version mismatch, safer pool joins, and better resilience for large fleets</li>
<li>Enhanced <strong><a href="https://severalnines.com/clustercontrol/solutions/kubernetes">Kubernetes</a> database support</strong> &mdash; clusters and backup schedules can now be deployed through GitOps as reviewable Git pull requests, plus structured logging, metrics, and default dashboards for observability, more reliable cluster health reporting, and operator compatibility and security updates</li>
<li><strong>Custom </strong><strong>pg_hba</strong><strong> configuration</strong> &mdash; define custom pg_hba.conf rules at deployment time, plus UI editing of entries &mdash; useful for multi-datacenter PostgreSQL topologies</li>
<li><strong>Audit log filtering and export</strong> in the UI</li>
<li><strong>ProxySQL management</strong> promoted from a pop-up dialog to a dedicated page</li>
<li><strong>Faster database deployments &mdash; </strong>multithreaded installation provisions 4 nodes in parallel for PostgreSQL and MySQL clusters, with optional additional customization.</li>
<li><strong>Redis / Valkey Sentinel logs</strong> now included in error reports for easier failover debugging</li>
<li><strong>Content-Security-Policy headers</strong> in the web UI, and improved resilience for long-running backup jobs</li>
</ul>
<h2 class="wp-block-heading">Get started today<a class="anchor-link" id="get-started-today"></a></h2>
<p>Get ClusterControl v2.5.0 as a new user by signing up for a free 30-day trial or upgrade your CC deployment to practically deploy ClickHouse or bring your current deployment under ClusterControl&rsquo;s management and access the other game-changing capabilities. In the meantime, full details can be found in the <a href="#">Release Notes</a>.</p>
<p>Questions about running ClickHouse or any other engine with ClusterControl?<a href="https://severalnines.com/contact"> Talk to us</a>.</p>
<h2 class="wp-block-heading" id="h-install-clustercontrol-in-10-minutes-free-30-day-enterprise-trial-included">Install ClusterControl in 10-minutes!<br> <strong>Free 30-day&nbsp;</strong>Enterprise trial included<a class="anchor-link" id="install-clustercontrol-in-10-minutes-free-30-day-enterprise-trial-included"></a></h2>
<h3 class="wp-block-heading" id="instructions">Script Installation Instructions<a class="anchor-link" id="script-installation-instructions"></a></h3>
<p>The installer script is the simplest way to get ClusterControl up and running. Run it on your chosen host, and it will take care of installing all required packages and dependencies.</p>
<p>Offline environments are supported as well. See the&nbsp;<a href="https://docs.severalnines.com/clustercontrol/latest/getting-started/installation/offline-installation/">Offline Installation</a>&nbsp;guide for more details.</p>
<p>On the ClusterControl server, run the following commands:</p>
<pre class="wp-block-code"><code>wget https://severalnines.com/downloads/cmon/install-cc
chmod +x install-cc
sudo ./install-cc     # omit sudo if you run as root</code></pre>
<p>After the installation is complete, open a web browser, navigate to&nbsp;<code>https://&lt;ClusterControl_host&gt;/</code>, and create the first admin user by entering a username (note that &ldquo;admin&rdquo; is reserved) and a password on the&nbsp;<a href="https://docs.severalnines.com/clustercontrol/latest/getting-started/quickstart/#step-2-create-the-first-admin-user">welcome page</a>. Once you&rsquo;re in, you can&nbsp;<a href="https://docs.severalnines.com/clustercontrol/latest/user-guide/deployment/create-database-cluster/">deploy</a>&nbsp;a new database cluster or&nbsp;<a href="https://docs.severalnines.com/clustercontrol/latest/user-guide/deployment/import-database-cluster/">import</a>&nbsp;an existing one.</p>
<p>The installer script supports a range of environment variables for advanced setup. You can define them using export or by prefixing the install command.</p>
<p>See the&nbsp;<a href="https://docs.severalnines.com/clustercontrol/latest/getting-started/installation/online-installation/#environment-variables">list of supported variables</a>&nbsp;and&nbsp;<a href="https://docs.severalnines.com/clustercontrol/latest/getting-started/installation/online-installation/#example-use-cases">example use cases</a>&nbsp;to tailor your installation.</p>
<p>The post <a href="https://severalnines.com/blog/clustercontrol-2-5-0-brings-clickhouse-support-to-on-prem-cloud-and-hybrid-environments/">ClusterControl 2.5.0 brings ClickHouse support to on-prem, cloud and hybrid environments</a> appeared first on <a href="https://severalnines.com">Severalnines</a>.</p>

<p><a href="https://severalnines.com/blog/clustercontrol-2-5-0-brings-clickhouse-support-to-on-prem-cloud-and-hybrid-environments/">ClusterControl 2.5.0 brings ClickHouse support to on-prem, cloud and hybrid environments</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MySQL 8.0.17 GTID Crash Safety Improvement</title>
      <link rel="alternate" type="text/html" href="https://jfg-mysql.blogspot.com/2026/08/mysql-8017-gtid-crash-safety-improvement.html" />
      <id>https://jfg-mysql.blogspot.com/2026/08/mysql-8017-gtid-crash-safety-improvement.html</id>
      <updated>2026-08-04T22:22:16+00:00</updated>
      <author><name>Jean-François Gagné</name></author>
      <summary type="html"><![CDATA[<p>I have known for some times that there is an interesting improvement in MySQL 8.0.17 regarding GTID Crash Safety, but I have not had the time nor the need to look into it before.&#160; When writing my last post (Understanding MySQL Replication \"fatal error 1236\": [...]), I saw something interesting related to this, and it is now time to cover this on my blog. From my point of view, this change is</p>
<p><a href="https://jfg-mysql.blogspot.com/2026/08/mysql-8017-gtid-crash-safety-improvement.html">MySQL 8.0.17 GTID Crash Safety Improvement</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>I have known for some times that there is an interesting improvement in MySQL 8.0.17 regarding GTID Crash Safety, but I have not had the time nor the need to look into it before.&amp;nbsp; When writing my last post (Understanding MySQL Replication "fatal error 1236": [&hellip;]), I saw something interesting related to this, and it is now time to cover this on my blog. From my point of view, this change is</p>

<p><a href="https://jfg-mysql.blogspot.com/2026/08/mysql-8017-gtid-crash-safety-improvement.html">MySQL 8.0.17 GTID Crash Safety Improvement</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>What Is Data Sovereignty and How Does MariaDB Protect Sovereignty in Cloud Databases?</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/what-is-data-sovereignty-and-how-does-mariadb-protect-sovereignty-in-cloud-databases/" />
      <id>https://mariadb.com/resources/blog/what-is-data-sovereignty-and-how-does-mariadb-protect-sovereignty-in-cloud-databases/</id>
      <updated>2026-08-04T18:33:49+00:00</updated>
      <author><name>Mani Nagasundaram</name></author>
      <summary type="html"><![CDATA[<p>Every organization now operates in a world where data doesn’t just need to be secure – it needs to be […]</p>
<p><a href="https://mariadb.com/resources/blog/what-is-data-sovereignty-and-how-does-mariadb-protect-sovereignty-in-cloud-databases/">What Is Data Sovereignty and How Does MariaDB Protect Sovereignty in Cloud Databases?</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Every organization now operates in a world where data doesn&rsquo;t just need to be secure &ndash; it needs to be sovereign. Where it&rsquo;s stored, who can access it, which laws govern it, and whether a foreign government can compel its disclosure are no longer legal footnotes. They&rsquo;re board-level risks, procurement requirements, and increasingly, deciding factors in which database a regulated business is even&hellip;</p>
<p><a href="https://mariadb.com/resources/blog/what-is-data-sovereignty-and-how-does-mariadb-protect-sovereignty-in-cloud-databases/" rel="nofollow">Source</a></p>

<p><a href="https://mariadb.com/resources/blog/what-is-data-sovereignty-and-how-does-mariadb-protect-sovereignty-in-cloud-databases/">What Is Data Sovereignty and How Does MariaDB Protect Sovereignty in Cloud Databases?</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Hear Ye, Hear Ye: A Guide to MariaDB’s Governance Model</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/hear-ye-hear-ye-a-guide-to-mariadbs-governance-model/" />
      <id>https://mariadb.org/hear-ye-hear-ye-a-guide-to-mariadbs-governance-model/</id>
      <updated>2026-08-04T18:07:22+00:00</updated>
      <author><name>Anna Widenius</name></author>
      <summary type="html"><![CDATA[<p>Be it known: MariaDB Server now has a clearer, publicly documented governance framework covering technical roles, subsystem ownership, decision-making, response expectations and continuity.<br />
Open source begins with access to the code. …<br />
Continue reading \"Hear Ye, Hear Ye: A Guide to MariaDB’s Governance Model\"<br />
Hear Ye, Hear Ye: A Guide to MariaDB’s Governance Model appeared first on MariaDB.org</p>
<p><a href="https://mariadb.org/hear-ye-hear-ye-a-guide-to-mariadbs-governance-model/">Hear Ye, Hear Ye: A Guide to MariaDB’s Governance Model</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Be it known: MariaDB Server now has a clearer, publicly documented governance framework covering technical roles, subsystem ownership, decision-making, response expectations and continuity.<br>
Open source begins with access to the code. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/hear-ye-hear-ye-a-guide-to-mariadbs-governance-model/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Hear Ye, Hear Ye: A Guide to MariaDB&rsquo;s Governance Model&rdquo;</span></a></p>
<p><a rel="nofollow" href="https://mariadb.org/hear-ye-hear-ye-a-guide-to-mariadbs-governance-model/">Hear Ye, Hear Ye: A Guide to MariaDB&rsquo;s Governance Model</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a></p>

<p><a href="https://mariadb.org/hear-ye-hear-ye-a-guide-to-mariadbs-governance-model/">Hear Ye, Hear Ye: A Guide to MariaDB’s Governance Model</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Understanding MySQL Replication &#8220;fatal error 1236&#8221;: &#8220;Replica has more GTIDs than the source has, using the source&#8217;s SERVER_UUID&#8221;</title>
      <link rel="alternate" type="text/html" href="https://jfg-mysql.blogspot.com/2026/08/understanding-mysql-replication-fatal-error-1236.html" />
      <id>https://jfg-mysql.blogspot.com/2026/08/understanding-mysql-replication-fatal-error-1236.html</id>
      <updated>2026-08-03T22:12:29+00:00</updated>
      <author><name>Jean-François Gagné</name></author>
      <summary type="html"><![CDATA[<p>This MySQL replication error&#160;— fatal error 1236&#160;/ Replica has more GTIDs than the source has, using the source\'s SERVER_UUID&#160;— shows the importance of thinking before acting. I am glad a non-DBA Colleague asked me about this error, because if he would just have restarted replication, it would have caused a much bigger mess.</p>
<p>Often, we are tempted&#160;— or pushed&#160;— to just</p>
<p><a href="https://jfg-mysql.blogspot.com/2026/08/understanding-mysql-replication-fatal-error-1236.html">Understanding MySQL Replication &#8220;fatal error 1236&#8221;: &#8220;Replica has more GTIDs than the source has, using the source&#8217;s SERVER_UUID&#8221;</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>This MySQL replication error&amp;nbsp;&mdash; fatal error 1236&amp;nbsp;/ Replica has more GTIDs than the source has, using the source&rsquo;s SERVER_UUID&amp;nbsp;&mdash; shows the importance of thinking before acting. I am glad a non-DBA Colleague asked me about this error, because if he would just have restarted replication, it would have caused a much bigger mess.</p>
<p>Often, we are tempted&amp;nbsp;&mdash; or pushed&amp;nbsp;&mdash; to just</p>

<p><a href="https://jfg-mysql.blogspot.com/2026/08/understanding-mysql-replication-fatal-error-1236.html">Understanding MySQL Replication &#8220;fatal error 1236&#8221;: &#8220;Replica has more GTIDs than the source has, using the source&#8217;s SERVER_UUID&#8221;</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Wirekite becomes Silver Sponsor of MariaDB Foundation</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/wirekite-becomes-silver-sponsor-of-mariadb-foundation/" />
      <id>https://mariadb.org/wirekite-becomes-silver-sponsor-of-mariadb-foundation/</id>
      <updated>2026-08-02T20:09:53+00:00</updated>
      <author><name>Anna Widenius</name></author>
      <summary type="html"><![CDATA[<p>We are pleased to welcome Wirekite as a new Silver Sponsor of MariaDB Foundation.<br />
Wirekite is an enterprise data movement platform focused on high-performance extract, load, migration, and replication workflows. …<br />
Continue reading \"Wirekite becomes Silver Sponsor of MariaDB Foundation\"<br />
The post Wirekite becomes Silver Sponsor of MariaDB Foundation appeared first on MariaDB.org.</p>
<p><a href="https://mariadb.org/wirekite-becomes-silver-sponsor-of-mariadb-foundation/">Wirekite becomes Silver Sponsor of MariaDB Foundation</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>We are pleased to welcome <a href="https://wirekite.io/">Wirekite</a> as a new <a href="https://mariadb.org/donate/#silver-tier-from-eur-5000-per-year">Silver Sponsor</a> of MariaDB Foundation.<br>
Wirekite is an enterprise data movement platform focused on high-performance extract, load, migration, and replication workflows. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/wirekite-becomes-silver-sponsor-of-mariadb-foundation/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Wirekite becomes Silver Sponsor of MariaDB Foundation&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/wirekite-becomes-silver-sponsor-of-mariadb-foundation/">Wirekite becomes Silver Sponsor of MariaDB Foundation</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p><a href="https://mariadb.org/wirekite-becomes-silver-sponsor-of-mariadb-foundation/">Wirekite becomes Silver Sponsor of MariaDB Foundation</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Stored Procedures memory consumption in Percona Server for MySQL</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/stored-procedures-memory-consumption-in-percona-server-for-mysql/" />
      <id>https://www.percona.com/blog/stored-procedures-memory-consumption-in-percona-server-for-mysql/</id>
      <updated>2026-07-31T11:50:51+00:00</updated>
      <author><name>Bogdan Degtyariov</name></author>
      <summary type="html"><![CDATA[<p>1. What it is about This investigation began as a performance comparison for different memory allocators. However, during benchmarking, I discovered unexpected effects deserving a more detailed explanation. I hope you find these findings both interesting and useful. Imagine you need to set up a MySQL database server. Every detail is planned: the operating system, … Continued<br />
The post Stored Procedures memory consumption in Percona Server for MySQL appeared first on Percona.</p>
<p><a href="https://www.percona.com/blog/stored-procedures-memory-consumption-in-percona-server-for-mysql/">Stored Procedures memory consumption in Percona Server for MySQL</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<h2><span style="font-weight: 400">1. What it is about</span><a class="anchor-link" id="1-what-it-is-about"></a></h2>
<p><span style="font-weight: 400">This investigation began as a performance comparison for different memory allocators. However, during benchmarking, I discovered unexpected effects deserving a more detailed explanation. I hope you find these findings both interesting and useful.</span></p>
<p><span style="font-weight: 400">Imagine you need to set up a MySQL database server. Every detail is planned: the operating system, the CPU architecture, the number of cores, the amount of RAM, the storage capacity and speed. On paper the hardware looks like it can handle the workload. But in reality, things rarely go exactly as planned. So, conducting a thorough stress test is the next thing to do.<br>
</span></p>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">2. Realities of stress testing</span><a class="anchor-link" id="2-realities-of-stress-testing"></a></h2>
<p><span style="font-weight: 400">You configure your MySQL server setting the </span><b>innodb_buffer_pool_size</b><span style="font-weight: 400"> to 70-80% of your available RAM. This creates a large fast buffer for your data and indexes, reducing the need for slower disk input/output.</span></p>
<p><span style="font-weight: 400">After a warmup period and a few hours of testing, everything looks great. The server is working at a steady pace, performance is stable. You tick the box &ndash; the server has passed the basic stress test. Thinking everything is fine, you consider leaving the test running over the weekend, expecting only minor fluctuations in performance.</span></p>
<p><span style="font-weight: 400">However, when you check the status the next morning, you find that the CPU is idle and the </span><span style="font-weight: 400">mysqld</span><span style="font-weight: 400"> process has vanished. Did it crash? You check the server error logs, but there is no record of a crash or a shutdown&mdash;not even a core dump. Then, you look at the system logs and find something unexpected:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">journalctl -k -g mysqld

Jun 09 07:29:14 beast-node7.tp.int.percona.com kernel: Out of memory:
Killed process 3936620 (mysqld) total-vm:194627592kB, anon-rss:183047480kB, file-rss:640kB, shmem-rss:0kB,
UID:955676158 pgtables:355860kB oom_score_adj:0</pre>
<p><span style="font-weight: 400">It appears that </span><span style="font-weight: 400">mysqld</span><span style="font-weight: 400"> ran out of memory and was terminated by the OOM (Out of Memory) killer after running for about 16 hours.</span></p>
<p><span style="font-weight: 400">We will focus on Resident Set Size (RSS), which is the subset of Virtual Memory Size (VSZ). RSS is the most significant part of VSZ and other parts like swap (only 8Gb) do not make notable contributions.</span></p>
<p><span style="font-weight: 400">The RSS reached 183GiB, significantly higher than the initial 145GiB (with the </span><b>innodb_buffer_pool_size</b><span style="font-weight: 400"> set to 135G). The </span><span style="font-weight: 400">mysqld</span><span style="font-weight: 400"> process had grabbed nearly 40GiB of extra memory, which at first looked like a memory leak. I ran my stress tests on different versions of MySQL and Percona Server and found a recurring pattern: memory usage climbed steadily until the system killed the process.</span></p>
<p><span style="font-weight: 400">I won&rsquo;t dive into the leak diagnosis here, but the result was clear: </span><span style="font-weight: 400">mysqld</span><span style="font-weight: 400"> wasn&rsquo;t leaking memory in the traditional sense. However, we still had to explain that 40GiB growth.<br>
</span></p>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">3. Configuration and methodology</span><a class="anchor-link" id="3-configuration-and-methodology"></a></h2>
<p><span style="font-weight: 400">The configuration was as follows:</span></p>
<table style="border-collapse: collapse;border: 1px solid #808080" border="1" cellpadding="5">
<tbody>
<tr>
<td><span style="font-weight: 400">Benchmark</span></td>
<td><span style="font-weight: 400">TPC-C via HammerDB 6.0</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">CPU</span></td>
<td><span style="font-weight: 400">Intel Xeon Gold 6230 (2&times;20 cores, HT = 80 logical CPUs)</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">RAM</span></td>
<td><span style="font-weight: 400">187 GiB DDR4</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Storage</span></td>
<td><span style="font-weight: 400">NVMe SSD (2.9 TB) INTEL SSDPE2KE032T8</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">OS</span></td>
<td><span style="font-weight: 400">Ubuntu 24.04, kernel 6.8.0-60-generic</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">DB Engines</span></td>
<td><span style="font-weight: 400">Percona Server 8.4.8-8 (release build)</span><span style="font-weight: 400"><br>
</span><span style="font-weight: 400">Percona Server 8.4.9-9 (internal build, unreleased)</span><span style="font-weight: 400">Percona Server 9.7.0 (internal build, unreleased)</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">The testing was done as follows:</span></p>
<table style="border-collapse: collapse;border: 1px solid #808080" border="1" cellpadding="5">
<tbody>
<tr>
<td><span style="font-weight: 400">Workload</span></td>
<td><span style="font-weight: 400">3000 warehouses (~300 GB data)</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Timing</span></td>
<td><span style="font-weight: 400">15 min ramp-up, 20 hours measurement window</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">Connections</span></td>
<td><span style="font-weight: 400">80 Virtual Users (to match the number of logical CPU cores). Connection lifetime is set for the entire duration of the test.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">InnoDB buffer sweep</span></td>
<td><span style="font-weight: 400">Starting from 150G down to 80G with 5G decrease</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">What we wanted to achieve:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Create conditions when memory allocations and deallocations inside the database server are frequent.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Utilize as much of physical memory as possible (at least 80%) by giving it to InnoDB Buffer Pool.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Use all available CPU resources in the most efficient way to prevent threads contesting for execution time (the number of connections should match the number of logical CPU cores).</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Eliminate any layers that add overhead and get in the way of direct measuring of allocators frequency and efficiency. The connections will be established using a socket file.</span></li>
</ul>
<p><span style="font-weight: 400">Servers configuration file:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag"># Make sure data dir is on NVMe
datadir=/nvme/data

# Thread Pool is enabled only for Percona Server
plugin-load-add=thread_pool.so
thread_pool_size=16
thread_pool_max_threads=5000
thread_pool_stall_limit=500

# Disable binary logging
skip-log-bin

# Connection settings
max_connections = 200

# Logging
log-error = /home/bogdan.degtyariov/servers/data/mysql-error.log
pid-file = /home/bogdan.degtyariov/servers/data/mysql.pid

# Socket
socket = /tmp/mysql-alloc-test.sock

# Disable SSL requirement
require_secure_transport = OFF

# Other settings
sql_mode = ""
wait_timeout = 288000        # 80 hours
interactive_timeout = 288000 # 80 hours

# Table settings
default-storage-engine = InnoDB

# InnoDB redo log configuration
innodb_redo_log_capacity = 32G

# Minimize flush overhead (not crash-safe, but optimal for testing)
innodb_flush_log_at_trx_commit = 0

# Memory configuration
innodb_buffer_pool_size = 150G # Configurable down to 80G
innodb_buffer_pool_instances = 16
innodb_io_capacity = 20000

# Performance optimizations
innodb_flush_method = O_DIRECT
innodb_log_buffer_size = 256M
innodb_doublewrite = OFF

# Transparent Huge Pages can be turned ON or OFF for the testing
large-pages = ON</pre>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">4. Where did the memory go?</span><a class="anchor-link" id="4-where-did-the-memory-go"></a></h2>
<p><span style="font-weight: 400">Memory management is complex, so let&rsquo;s simplify. Applications rarely talk directly to the Linux kernel because the kernel typically works in 4KB pages, which is inefficient for developers. Instead, applications use allocators like </span><span style="font-weight: 400">glibc</span><span style="font-weight: 400"> malloc, </span><span style="font-weight: 400">jemalloc</span><span style="font-weight: 400">, or </span><span style="font-weight: 400">tcmalloc</span><span style="font-weight: 400">. These tools handle memory operations by minimizing overhead, managing bookkeeping, and preventing fragmentation. Most importantly, they use caching.</span></p>
<p><span style="font-weight: 400">When a program frees memory, the allocator rarely returns it to the OS immediately. Instead, it moves that memory into an internal &ldquo;free-list&rdquo; cache. Reusing memory from this cache is much faster than requesting new memory from the kernel.</span></p>
<p><span style="font-weight: 400">Also, the Percona Server for MySQL and upstream MySQL Server use their own implementation of the memory arena allocator called MEM_ROOT. Historically MEM_ROT was architected decades ago when the standard Linux implementation of </span><span style="font-weight: 400">glibc</span><span style="font-weight: 400"> memory allocator was slow and prone to lock contention in multithreaded programs.</span></p>
<p><span style="font-weight: 400">Enabling memory profiling revealed that MEM_ROOT allocations for cursor metadata in stored routines was responsible for most of the additional memory acquired by the server process:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">sp_head::execute_procedure           (TPC-C stored procedure)
   &#9492;&#9472; sp_instr_copen::execute        (OPEN &lt;cursor&gt; statement)
       &#9492;&#9472; sp_cursor::open
           &#9492;&#9472; mysql_open_cursor
               &#9500;&#9472; Materialized_cursor::send_result_set_metadata  87831 MB (94.3%)
               &#9492;&#9472; Query_result_materialize::start_execution      5311 MB  (5.7%)
                   &#9492;&#9472; MEM_ROOT::Alloc / AllocBlock / ForceNewBlock</pre>
<p><span style="font-weight: 400"><strong>NOTE:</strong> 87G is a significant growth of memory allocation considering that in that run the server initially allocated ~85G with Innodb_buffer_pool_size=80G.</span></p>
<p><span style="font-weight: 400">The problem happens regardless of the data size because the actual issue is in stored routines cursor metadata. When the stored procedure is called the memory allocated for cursor metadata is not freed. Over the course of many repeated calls to the same stored procedure the cumulative amount of memory for the cursor can reach any value.</span></p>
<p><span style="font-weight: 400">The following graph demonstrates the memory growth in Percona Server 8.4.8-8 from ~80G to over ~180G in RSS and over 200G VSZ over the period of 24 hours.</span></p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-50941 size-full" src="https://www.percona.com/wp-content/uploads/2026/07/rss-vsz.png" alt="" width="1043" height="654"></p>
<p><span style="font-weight: 400">Thus, a bug was reported for Percona Server: </span><a href="https://perconadev.atlassian.net/browse/PS-11472"><span style="font-weight: 400">https://perconadev.atlassian.net/browse/PS-11472</span></a></p>
<p><span style="font-weight: 400">With Percona Server for MySQL 9.7.0-1 the RSS/VSZ growth was at a slower rate, but still noticeable and it was not flattening towards a stable horizontal line (the server was configured with a small amount of memory for innodb_buffer_pool_size=4G and run for 5 hours instead of 20).</span></p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-50945" src="https://www.percona.com/wp-content/uploads/2026/07/rss-vsz-ps-9.7.0.jpg" alt="" width="1043" height="663"></p>
<p><span style="font-weight: 400">Memory profiling showed the new allocations in version 9.7.0-1 were in the same place where cursor metadata is handled:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">sp_head::execute_procedure           (TPC-C stored procedure)
   &#9492;&#9472; sp_instr_copen::execute        (OPEN &lt;cursor&gt; statement)
       &#9492;&#9472; sp_cursor::open
           &#9492;&#9472; mysql_open_cursor
               &#9500;&#9472; Materialized_cursor::send_result_set_metadata
               &#9492;&#9472; Query_result_materialize::start_execution      
                   &#9492;&#9472; MEM_ROOT::Alloc / AllocBlock / ForceNewBlock 4,025.8 MB (99.6%)</pre>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">5. Possible workarounds</span><a class="anchor-link" id="5-possible-workarounds"></a></h2>
<p><span style="font-weight: 400">My tests showed that OOM crashes happened consistently under two specific conditions:,</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">Connections are never closed and stay open permanently</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Connections ran queries at maximum speed without any pauses</span></li>
</ol>
<p><span style="font-weight: 400">Also, when the connection lifetime was limited and users were made to close connection and reconnect after 1M transactions, the memory exhaustion stopped, and memory was freed correctly &ndash; all with only a minor impact on performance. To minimize the delays associated with creating a new connection thread on the server I used the connection pool functionality in HammerDB. When the connection lifetime is ended, the actual connection is not closed, but &ldquo;reset&rdquo; and reused. This frees the context accumulated during the connection activity and stimulates returning memory to the OS. This connection pool mechanism is more efficient than the open/close cycle for maintaining the connection lifetime.&nbsp;</span></p>
<p><span style="font-weight: 400">I had two runs with reconnecting users: with and without connection pool. The graph demonstrates that using the pool improves the performance in this test.</span></p>
<p><span style="font-weight: 400">Also, during another experiment with unlimited connection lifetime, adding a 0.5ms pause after a few transactions prevented the crashes, though performance dropped slightly more.</span></p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-50947" src="https://www.percona.com/wp-content/uploads/2026/07/qps-delay-1.jpg" alt="" width="1043" height="631"></p>
<p><span style="font-weight: 400">The memory graphs have consistent periodic oscillations that never reach into the dangerous zone.</span></p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-50949" src="https://www.percona.com/wp-content/uploads/2026/07/zigzag.jpg" alt="" width="1043" height="656"></p>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">6. Summary</span><a class="anchor-link" id="6-summary"></a></h2>
<p><span style="font-weight: 400">To sum it up: the observed MySQL&rsquo;s memory bloating is caused by a problem in the server cursor implementation not freeing metadata memory.&nbsp;</span></p>
<p><span style="font-weight: 400">Under heavy, constant load, that memory accumulates to the amount which eventually causes an OOM crash. Capping how long connections stay active or adding a short pause between transactions, gives the server time to clean itself up. Normally the client side processing adds such pauses without need to do it on purpose.</span></p>
<p><span style="font-weight: 400">Finally, it is important to remember that the best benchmark results do not always guarantee the best real-life performance.</span></p>
<p>The post <a href="https://www.percona.com/blog/stored-procedures-memory-consumption-in-percona-server-for-mysql/">Stored Procedures memory consumption in Percona Server for MySQL</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p><a href="https://www.percona.com/blog/stored-procedures-memory-consumption-in-percona-server-for-mysql/">Stored Procedures memory consumption in Percona Server for MySQL</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Percona Server for MongoDB 8.3 Technical Preview Is Now Available</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/percona-server-for-mongodb-8-3-technical-preview-is-now-available/" />
      <id>https://www.percona.com/blog/percona-server-for-mongodb-8-3-technical-preview-is-now-available/</id>
      <updated>2026-07-30T17:36:14+00:00</updated>
      <author><name>Radoslaw Szulgo</name></author>
      <summary type="html"><![CDATA[<p>Percona Server for MongoDB 8.3 is available today as a Technical Preview. It is not for production. It is for your lab, your staging cluster, and your benchmark harness – and for sharing with us what works and what does not. Especially if this version is your segue to leverage upcoming full-text and vector search … Continued<br />
The post Percona Server for MongoDB 8.3 Technical Preview Is Now Available appeared first on Percona.</p>
<p><a href="https://www.percona.com/blog/percona-server-for-mongodb-8-3-technical-preview-is-now-available/">Percona Server for MongoDB 8.3 Technical Preview Is Now Available</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><b>Percona Server for MongoDB 8.3 is available today as a Technical Preview. It is not for production. It is for your lab, your staging cluster, and your benchmark harness &ndash; and for sharing with us what works and what does not. Especially if this version is your segue to leverage upcoming full-text and vector search capabilities. Many users have been asking when Percona would ship 8.2 or 8.3 &ndash; this is the answer. We are jumping straight to 8.3. In this blog post, I share everything you have to know before upgrading to 8.3. I gathered all available information, so you don&rsquo;t need to.&nbsp;&nbsp;</b></p>
<h2><span style="font-weight: 400">Why this release matters</span><a class="anchor-link" id="why-this-release-matters"></a></h2>
<p><span style="font-weight: 400">MongoDB 8.3 Community went GA in May 2026. This was the fourth significant MongoDB release in nearly 2 years. I have observed that the upstream MongoDB Community project is now moving faster than most organizations&rsquo; upgrades (adoption telemetry data later in this blog post). Nonetheless, Percona&rsquo;s job is to ensure that the free, enterprise-grade path does not fall behind, and you still can get the performance of the current release without giving up data-at-rest encryption with KMIP, HashiCorp Vault, or OpenBao, audit logging, external LDAP authentication, OpenID Connect, File copy-based initial sync, in-memory engine, or audit log with log redaction. All of that stays in Percona Server for MongoDB 8.3, and it stays free and open. This is the Percona way.</span></p>
<p><span style="font-weight: 400">I&rsquo;ll not discover America by writing that the data layer now has to move at AI speed. Application teams are shipping agentic workloads today, and that&rsquo;s why performance and functional requirements are growing exponentially. Two years ago, we experienced occasional query retries and recall storms, critical security patches, and multi-region deployments could trade compliance for latency. Now, this is bread-and-butter we need to deal with every day.&nbsp;</span></p>
<p><span style="font-weight: 400">I&rsquo;m happy to share that Percona has published Percona Server for MongoDB 8.3 in Technical Preview today. 8.3 is not one release forward from 8.0 &ndash; it is three. Everything that landed in the two minor releases (and 8.1 Rapid Release) in between arrives at once. Percona Server for MongoDB 8.3 is the most performant release so far! And besides the performance boost and many functional improvements, I&rsquo;ve described below, I&rsquo;m personally most excited about the fact that this release enables full-text search and vector search capabilities, so you can finally equip your applications with AI power using the same MongoDB. More about that in my next blog post &ndash; brace yourself! </span></p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-50878 size-large" src="https://www.percona.com/wp-content/uploads/2026/07/psmdb-83-highlights-1024x576.png" alt="" width="1024" height="576"></p>
<h2><span style="font-weight: 400">What&rsquo;s new compared to 8.0</span><a class="anchor-link" id="whats-new-compared-to-8-0"></a></h2>
<p><span style="font-weight: 400">Because we&rsquo;re skipping 8.1 and 8.2 releases, the delta is large. I grouped the highlights by what you&rsquo;d actually notice.</span></p>
<h3><b>Performance</b><a class="anchor-link" id="performance"></a></h3>
<p><span style="font-weight: 400">Percona Server for MongoDB inherits performance optimization from the upstream MongoDB Community:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Up to </span><b>195%</b><span style="font-weight: 400"> higher throughput on time-series bulk insertions</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Up to </span><b>40%</b><span style="font-weight: 400"> on match filter queries</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Up to </span><b>20%</b><span style="font-weight: 400"> on queries against documents with arrays</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Up to </span><b>10%</b><span style="font-weight: 400"> on in-cache read workloads</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Up to </span><b>5%</b><span style="font-weight: 400"> reduction in CPU utilization</span></li>
</ul>
<p><span style="font-weight: 400">Moreover, Percona Server for MongoDB 8.3 now offers faster initial sync, faster time-series bulk inserts, and reduced multi-planning costs for queries. As always, your mileage depends entirely on the shape of your workload.</span></p>
<h3><b>Query planning</b><a class="anchor-link" id="query-planning"></a></h3>
<p><span style="font-weight: 400">The </span><b>Cost-Based Ranker (CBR)</b><span style="font-weight: 400"> is now the default plan selection mechanism for eligible queries. Multi-planning gets a short trial period; if it can&rsquo;t settle on a plan, CBR evaluates each plan node by estimated cost. New </span><span style="font-weight: 400">serverStatus</span><span style="font-weight: 400"> counters under </span><span style="font-weight: 400">metrics.query.cbr</span><span style="font-weight: 400"> and new </span><span style="font-weight: 400">explain</span><span style="font-weight: 400"> output let you see when CBR was used and how often it won.&nbsp;</span></p>
<h3><b>Query language and aggregation</b><a class="anchor-link" id="query-language-and-aggregation"></a></h3>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Native hybrid search (full-text search and vector search) is enabled with Percona Search for MongoDB (fork of </span><a href="https://github.com/mongodb/mongot"><span style="font-weight: 400">mongodb/mongot</span></a><span style="font-weight: 400"> project) via</span><span style="font-weight: 400"> $scoreFusion</span><span style="font-weight: 400"> and </span><span style="font-weight: 400">$rankFusion</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Array element indexes are now accessible in </span><span style="font-weight: 400">$map</span><span style="font-weight: 400">, </span><span style="font-weight: 400">$filter</span><span style="font-weight: 400">, and </span><span style="font-weight: 400">$reduce</span><span style="font-weight: 400"> via the new </span><span style="font-weight: 400">arrayIndexAs</span><span style="font-weight: 400"> field and the </span><span style="font-weight: 400">$$IDX</span><span style="font-weight: 400"> system variable</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">New expressions: </span><span style="font-weight: 400">$subtype</span><span style="font-weight: 400">, </span><span style="font-weight: 400">$createObjectId</span><span style="font-weight: 400">, </span><span style="font-weight: 400">$hash</span><span style="font-weight: 400">, </span><span style="font-weight: 400">$hexHash</span><span style="font-weight: 400">, </span><span style="font-weight: 400">$serializeEJSON</span><span style="font-weight: 400">, </span><span style="font-weight: 400">$deserializeEJSON</span><span style="font-weight: 400">, </span><span style="font-weight: 400">$currentDate</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">$convert</span><span style="font-weight: 400"> gains a </span><span style="font-weight: 400">base</span><span style="font-weight: 400"> argument (base 2/8/10/16) and can convert strings representing arrays and objects, plus BinData <img decoding="async" src="https://s.w.org/images/core/emoji/17.0.2/72x72/2194.png" alt="&harr;" class="wp-smiley" style="height: 1em;max-height: 1em"> numeric arrays in both directions</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">$mergeObjects</span><span style="font-weight: 400"> now works inside </span><span style="font-weight: 400">$setWindowFields</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">$concatArrays</span><span style="font-weight: 400"> and </span><span style="font-weight: 400">$setUnion</span><span style="font-weight: 400"> accumulators, the </span><span style="font-weight: 400">$listClusterCatalog</span><span style="font-weight: 400"> stage, and </span><span style="font-weight: 400">$lookup</span><span style="font-weight: 400"> across multiple encrypted collections for CSFLE and Queryable Encryption</span></li>
</ul>
<h3><b>Storage and operations</b><a class="anchor-link" id="storage-and-operations"></a></h3>
<p><span style="font-weight: 400">The most operationally useful change for anyone running in containers is the </span><b>WiredTiger</b> <b>cache size, which can now be set as a percentage</b><span style="font-weight: 400"> of available memory via </span><span style="font-weight: 400">&ndash;wiredTigerCacheSizePct</span><span style="font-weight: 400"> / </span><span style="font-weight: 400">storage.wiredTiger.engineConfig.cacheSizePct</span><span style="font-weight: 400">, instead of a fixed GB value. If you run Percona Server for MongoDB on Kubernetes via our Percona Operator for MongoDB. This alone is worth the upgrade!</span></p>
<p><span style="font-weight: 400">Also worth knowing:</span></p>
<ul>
<li style="font-weight: 400"><b>zstd negative compression levels</b><span style="font-weight: 400"> &ndash; the supported range widens to -7 through 22, trading ratio for speed</span></li>
<li style="font-weight: 400"><b>Initial-sync index builds</b><span style="font-weight: 400"> now use 10% of available RAM by default, tunable, and bounded</span></li>
<li style="font-weight: 400"><b>terminateSecondaryReadsOnOrphanCleanup</b><span style="font-weight: 400"> (on by default) terminates long-running secondary reads that started before a chunk migration was committed. Previously, those reads continued and could silently return incomplete results with no error. </span><span style="font-weight: 400">orphanCleanupDelaySecs</span><span style="font-weight: 400"> moves from 900 to 3600 to accommodate this.</span></li>
</ul>
<h3><b>Sharding</b><a class="anchor-link" id="sharding"></a></h3>
<ul>
<li style="font-weight: 400"><b>removeShard</b><b> is deprecated</b><span style="font-weight: 400">, replaced by four commands that give you granular control over draining and removal: </span><span style="font-weight: 400">startShardDraining</span><span style="font-weight: 400">, </span><span style="font-weight: 400">stopShardDraining</span><span style="font-weight: 400">, </span><span style="font-weight: 400">shardDrainingStatus</span><span style="font-weight: 400">, </span><span style="font-weight: 400">commitShardRemoval</span><span style="font-weight: 400">. A parallel set exists for the embedded-to-dedicated config server transition.</span></li>
<li style="font-weight: 400"><b>Sharded clusters: DDL operations and </b><b>applyOps</b><b> can now run only on </b><b>mongos</b><b> across</b><span style="font-weight: 400"> all sharded clusters.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">New </span><span style="font-weight: 400">mongod &ndash;replicaSetConfigShardMaintenanceMode</span><span style="font-weight: 400"> converts a replica set primary directly into an embedded config shard, skipping the dedicated config server replica set step.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Shard-level query stats now include queries that originated on </span><span style="font-weight: 400">mongos</span><span style="font-weight: 400">. Previously, most forwarded queries were invisible in shard-level stats.</span></li>
</ul>
<h3><b>Security</b><a class="anchor-link" id="security"></a></h3>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Software Bills of Materials (SBOMs) are attached to our deliverables across all release distribution channels. SBOMs improve software supply chain transparency by documenting the components and dependencies included in a build. They are generated automatically as part of the release pipeline in the industry-standard </span><a href="https://cyclonedx.org/specification/overview/"><span style="font-weight: 400">CycloneDX</span></a><span style="font-weight: 400"> format.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">New pre-auth connection resource limits (</span><span style="font-weight: 400">capMemoryConsumptionForPreAuthBuffers</span><span style="font-weight: 400">, </span><span style="font-weight: 400">preAuthMaximumMessageSizeBytes</span><span style="font-weight: 400">, </span><span style="font-weight: 400">messageSizeErrorRateSec</span><span style="font-weight: 400">)</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Ingress connection establishment rate limiting and per-application exemptions from ingress request rate limiting. See </span><a href="https://www.mongodb.com/docs/manual/reference/parameters/#mongodb-parameter-param.ingressRequestRateLimiterApplicationExemptions"><span style="font-weight: 400">ingressRequestRateLimiterApplicationExemptions</span><span style="font-weight: 400">.</span></a><span style="font-weight: 400"> for more.</span></li>
</ul>
<h3><b>Observability</b><a class="anchor-link" id="observability"></a></h3>
<ul>
<li style="font-weight: 400"><b>Query memory tracking</b><span style="font-weight: 400">: </span><span style="font-weight: 400">inUseTrackedMemBytes</span><span style="font-weight: 400"> and </span><span style="font-weight: 400">peakTrackedMemBytes</span><span style="font-weight: 400"> in </span><span style="font-weight: 400">$currentOp</span><span style="font-weight: 400">, profiler output, slow query logs, explain results, and </span><span style="font-weight: 400">$planCacheStats</span></li>
<li style="font-weight: 400"><b>Slow in-progress query logs</b><span style="font-weight: 400">: a lightweight entry emitted once per query when it exceeds </span><span style="font-weight: 400">slowOpInProgressThreshold</span><span style="font-weight: 400">, so you can see a slow query while it is still running rather than after it finishes</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">FTDC now collects </span><span style="font-weight: 400">connPoolStats</span><span style="font-weight: 400"> for </span><span style="font-weight: 400">mongod</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Standardized disk-spill metrics (</span><span style="font-weight: 400">spills</span><span style="font-weight: 400">, </span><span style="font-weight: 400">spilledBytes</span><span style="font-weight: 400">, </span><span style="font-weight: 400">spilledRecords</span><span style="font-weight: 400">, </span><span style="font-weight: 400">spilledDataStorageSize</span><span style="font-weight: 400">) in explain output</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">New TTL, replication lag, and admission control metrics in </span><span style="font-weight: 400">serverStatus</span></li>
</ul>
<h2>What&rsquo;s changed: Read this before you upgrade<a class="anchor-link" id="whats-changed-read-this-before-you-upgrade"></a></h2>
<p><b>Upgrade path.</b><span style="font-weight: 400"> To go from 8.0 directly to 8.3, your 8.0 deployment must have </span><span style="font-weight: 400">featureCompatibilityVersion</span><span style="font-weight: 400"> set to </span><span style="font-weight: 400">8.0</span><span style="font-weight: 400">. Verify with </span><span style="font-weight: 400">db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })</span><span style="font-weight: 400">. All cluster members must be running before you start.</span></p>
<p><b>Geospatial indexes may need rebuilding.</b><span style="font-weight: 400"> Index generation now prioritizes GeoJSON over legacy numeric coordinates when a document contains both. If your documents have legacy numeric coordinates preceding GeoJSON coordinates and your existing indexes depend on the old ordering, rebuild them and re-verify your geospatial query results. Separately, </span><span style="font-weight: 400">2dsphereIndexVersion</span><span style="font-weight: 400"> now defaults to 4.</span></p>
<p><b>Error codes changed.</b><span style="font-weight: 400"> Exceeding the </span><span style="font-weight: 400">$facet</span><span style="font-weight: 400"> 100 MB limit now returns </span><span style="font-weight: 400">ExceededMemoryLimit</span><span style="font-weight: 400"> (146) instead of </span><span style="font-weight: 400">4031700</span><span style="font-weight: 400">. Upserts producing an oversized BSON object return </span><span style="font-weight: 400">10334</span> <span style="font-weight: 400">BSONObjectTooLarge</span><span style="font-weight: 400"> instead of </span><span style="font-weight: 400">17419</span><span style="font-weight: 400">/</span><span style="font-weight: 400">17420</span><span style="font-weight: 400">. Anything in your stack that string-matches error codes needs updating.</span></p>
<p><b>$text</b><b> sorted-by-score queries can now fail.</b><span style="font-weight: 400"> The </span><span style="font-weight: 400">TextOr</span><span style="font-weight: 400"> stage is capped at 100 MB. With </span><span style="font-weight: 400">allowDiskUse: true,</span><span style="font-weight: 400"> it spills; with </span><span style="font-weight: 400">false</span><span style="font-weight: 400"> the query errors out. Previously, it was unbounded, which is to say, previously, it could OOM your node instead.</span></p>
<p><b>Pre-epoch date arithmetic shifts by one second.</b> <span style="font-weight: 400">$dateAdd</span><span style="font-weight: 400"> and </span><span style="font-weight: 400">$dateSubtract</span><span style="font-weight: 400">, with a non-millisecond unit, on dates before 1970-01-01 now return a result that is one second greater. This propagates into </span><span style="font-weight: 400">$setWindowFields</span><span style="font-weight: 400"> and </span><span style="font-weight: 400">$densify</span><span style="font-weight: 400">.</span></p>
<p><b>Monitoring integrations will need attention.</b><span style="font-weight: 400"> The </span><span style="font-weight: 400">service</span><span style="font-weight: 400"> field is removed from </span><span style="font-weight: 400">serverStatus</span><span style="font-weight: 400"> output, and </span><span style="font-weight: 400">cpuNanos</span><span style="font-weight: 400"> is moved from </span><span style="font-weight: 400">operationMetrics</span><span style="font-weight: 400"> into </span><span style="font-weight: 400">$queryStats</span><span style="font-weight: 400"> (Linux only).</span></p>
<p><b>Other behavior changes:</b> <span style="font-weight: 400">$$CLUSTER_TIME</span><span style="font-weight: 400"> now throws an error in standalone deployments. </span><span style="font-weight: 400">db.collection.validate({full: true})</span><span style="font-weight: 400"> no longer implicitly enables </span><span style="font-weight: 400">checkBSONConformance</span><span style="font-weight: 400">. </span><span style="font-weight: 400">explain()</span><span style="font-weight: 400"> against a non-existent database on a sharded cluster no longer creates the database. Time series collections reject a </span><span style="font-weight: 400">timeField</span><span style="font-weight: 400"> starting with </span><span style="font-weight: 400">$</span><span style="font-weight: 400"> and reject an index named or hinted </span><span style="font-weight: 400">&ldquo;_id_&rdquo;</span><span style="font-weight: 400">.</span></p>
<p><b>One-way doors from sharding to the replica set.</b><span style="font-weight: 400"> A replica set that was previously a sharded cluster cannot be converted back into a sharded cluster &ndash; residual sharding metadata blocks it. And downgrading from 8.3 requires you to first drop 2dsphere version 4 indexes and update or drop any views, validators, or collection validation rules that use 8.3-only expressions.</span></p>
<h2><span style="font-weight: 400">Don&rsquo;t stay behind: What the telemetry says about version adoption</span><a class="anchor-link" id="dont-stay-behind-what-the-telemetry-says-about-version-adoption"></a></h2>
<p><span style="font-weight: 400">We analyzed anonymous product telemetry from Percona Server for MongoDB over the last 12 months to assess the share of active database instances. </span><b>Major version adoption takes more than a year for many users and organizations.</b><span style="font-weight: 400"> PSMDB 8.0 went from roughly a fifth of the installed base to nearly half over twelve months. That is healthy, and it is also slower than a release cadence of four significant upstream versions in 19 months. The gap between how quickly MongoDB ships and how quickly the installed base moves is why we are publishing a Technical Preview instead of delaying until a GA build. Also, that&rsquo;s a strong signal to move to version 8.0 if you haven&rsquo;t already, and then explore 8.3.&nbsp;</span></p>
<p><span style="font-weight: 400">Moreover, if you run (or consider running) your Percona Server for MongoDB </span><b>in a container</b> <b>deployment &ndash;</b><span style="font-weight: 400"> Containerized deployments now account for about 24% of active instances, up from roughly 21% a year ago &ndash; so still Docker and Kubernetes for MongoDB are the thing, and this release improvement of percentage-based WiredTiger cache sizing is probably the most immediately useful thing.</span></p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-50879 size-large" src="https://www.percona.com/wp-content/uploads/2026/07/psmdb-version-adoption-360d-e1785430024956-1024x488.png" alt="" width="1024" height="488"></p>
<p>&nbsp;</p>
<table style="height: 135px" width="1099">
<thead>
<tr>
<th><b>Version</b></th>
<th><b>Aug 2025</b></th>
<th><b>Jul 2026</b></th>
<th><b>Change</b></th>
</tr>
</thead>
<tbody>
<tr>
<td><b>8.0</b></td>
<td><span style="font-weight: 400">22.6%</span></td>
<td><span style="font-weight: 400">47.6%</span></td>
<td><span style="font-weight: 400">+25.0 pts</span></td>
</tr>
<tr>
<td><b>7.0</b></td>
<td><span style="font-weight: 400">28.7%</span></td>
<td><span style="font-weight: 400">33.1%</span></td>
<td><span style="font-weight: 400">+4.4 pts</span></td>
</tr>
<tr>
<td><b>6.0</b></td>
<td><span style="font-weight: 400">26.9%</span></td>
<td><span style="font-weight: 400">16.0%</span></td>
<td><span style="font-weight: 400">&minus;10.9 pts</span></td>
</tr>
<tr>
<td><b>5.0</b></td>
<td><span style="font-weight: 400">22.9%</span></td>
<td><span style="font-weight: 400">3.6%</span></td>
<td><span style="font-weight: 400">&minus;19.3 pts</span></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<h2><span style="font-weight: 400">Technical Preview: What does that actually mean</span><a class="anchor-link" id="technical-preview-what-does-that-actually-mean"></a></h2>
<p><span style="font-weight: 400">A Technical Preview build is complete enough to install, benchmark, and evaluate. It meets equal quality and packaging requirements as any other 8.0 patch. However, it has not undergone the full release qualification we require for a GA release with respect to ecosystem compatibility:</span></p>
<ul>
<li style="font-weight: 400"><b>Percona Backup for MongoDB (PBM)</b><span style="font-weight: 400">: logical backup and restore against an 8.3 server may cause some issues. Do not rely on it to protect anything you care about. Current known limitations are around logical backup and restore, and PITR. If you&rsquo;re interested in details and progress on those, follow our </span><a href="https://perconadev.atlassian.net/browse/PSMDB-2176"><span style="font-weight: 400">Jira tickets</span></a><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><b>Percona ClusterSync for MongoDB (PCSM)</b><span style="font-weight: 400">: replication to or from an 8.3 cluster is unvalidated.</span></li>
<li style="font-weight: 400"><b>Percona Monitoring and Management (PMM):</b><span style="font-weight: 400"> metric collection may be incomplete or fail outright, particularly given the </span><span style="font-weight: 400">serverStatus</span><span style="font-weight: 400"> and </span><span style="font-weight: 400">cpuNanos</span><span style="font-weight: 400"> changes described above.</span></li>
</ul>
<p><span style="font-weight: 400">To be clear about what this is and isn&rsquo;t: upstream MongoDB Community 8.3 is a stable, production-suitable release with support through October 2029. The Technical Preview label applies to </span><b>Percona&rsquo;s build</b><span style="font-weight: 400">, not to MongoDB 8.3 itself. It reflects where we are in qualifying our surrounding tooling against this version, and not a judgment about upstream stability.</span></p>
<p><span style="font-weight: 400">So if you need a fully-integrated Percona Server for MongoDB today, </span><b>8.0 is still the answer</b><span style="font-weight: 400">. The current release is 8.0.26-11. When our 8.3 build reaches GA, that changes.</span></p>
<h2><span style="font-weight: 400">How to start</span><a class="anchor-link" id="how-to-start"></a></h2>
<p><span style="font-weight: 400">We recommend installing (or upgrading) Percona Server for MongoDB using the official Percona repositories via the </span><a href="https://docs.percona.com/percona-software-repositories/index.html"><span style="font-weight: 400">percona-release repository management tool</span></a><span style="font-weight: 400"> and your system&rsquo;s package manager. For further instructions, start with the <a href="https://docs.percona.com/percona-server-for-mongodb/8.3/install/index.html">quickstart guide</a> for the fresh installation or an </span><a href="https://docs.percona.com/percona-server-for-mongodb/8.3/install/upgrade-from-80.html"><span style="font-weight: 400">upgrade procedure</span></a> from 8.0.</p>
<h2><span style="font-weight: 400">Feedback needed</span><a class="anchor-link" id="feedback-needed"></a></h2>
<p><span style="font-weight: 400">This is the part that matters. A Technical Preview is only worth shipping if it yields findings.</span></p>
<p><span style="font-weight: 400">We are specifically interested in:</span></p>
<ul>
<li style="font-weight: 400"><b>Percona-specific feature behavior</b><span style="font-weight: 400">: data-at-rest encryption with KMIP or Vault, audit logging, external LDAP authentication, OpenID Connect, file copy-based initial sync, hot backup, and audit logging with log redaction.</span></li>
<li style="font-weight: 400"><b>PBM, PCSM, and PMM interactions.</b><span style="font-weight: 400"> We know these might be limited. Knowing </span><i><span style="font-weight: 400">how</span></i><span style="font-weight: 400"> they fail helps us prioritize and fix them within the next release cycle.</span></li>
<li style="font-weight: 400"><b>Upgrade friction</b><span style="font-weight: 400"> from 8.0, especially on sharded clusters and around the geospatial index and </span><span style="font-weight: 400">removeShard</span><span style="font-weight: 400"> changes.</span></li>
</ul>
<p><span style="font-weight: 400">Post your findings in the </span><a href="https://forums.percona.com/c/mongodb/percona-server-for-mongodb/17"><b>Percona Server for MongoDB forum</b></a><span style="font-weight: 400">. Include your topology, your workload shape, and the exact version you tested. Engineering reads that category directly, and feedback from this preview will shape what the GA build looks like.</span></p>
<p><span style="font-weight: 400">If you find a security issue, please report it through </span><a href="https://www.percona.com/security/"><span style="font-weight: 400">Percona Security</span></a><span style="font-weight: 400"> disclosure process rather than posting publicly.</span></p>
<hr>
<p><i><span style="font-weight: 400">Percona Server for MongoDB is a free, source-available, drop-in replacement for MongoDB Community Edition with enterprise-grade features. Telemetry figures in this post are derived from anonymous product telemetry.</span></i></p>
<p>The post <a href="https://www.percona.com/blog/percona-server-for-mongodb-8-3-technical-preview-is-now-available/">Percona Server for MongoDB 8.3 Technical Preview Is Now Available</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p><a href="https://www.percona.com/blog/percona-server-for-mongodb-8-3-technical-preview-is-now-available/">Percona Server for MongoDB 8.3 Technical Preview Is Now Available</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MySQL Best Practice : not using date / time types, nor ENUM</title>
      <link rel="alternate" type="text/html" href="https://jfg-mysql.blogspot.com/2026/07/best-practice-no-timestamp-nor-enum.html" />
      <id>https://jfg-mysql.blogspot.com/2026/07/best-practice-no-timestamp-nor-enum.html</id>
      <updated>2026-07-30T15:46:44+00:00</updated>
      <author><name>Jean-François Gagné</name></author>
      <summary type="html"><![CDATA[<p>Today, I was reminded of a MySQL Best Practice, probably generalizable to all databases : using simple types, not complex types.&#160; Such complex types to avoid include the date and time data types (including TIMESTAMP) and ENUM.&#160; Let\'s see why.</p>
<p>A little history about this, Baron Schwartz, a MySQL Legend who is not involved in the community anymore, compared using the TIMESTAMP type to</p>
<p><a href="https://jfg-mysql.blogspot.com/2026/07/best-practice-no-timestamp-nor-enum.html">MySQL Best Practice : not using date / time types, nor ENUM</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Today, I was reminded of a MySQL Best Practice, probably generalizable to all databases : using simple types, not complex types.&amp;nbsp; Such complex types to avoid include the date and time data types (including TIMESTAMP) and ENUM.&amp;nbsp; Let&rsquo;s see why.</p>
<p>A little history about this, Baron Schwartz, a MySQL Legend who is not involved in the community anymore, compared using the TIMESTAMP type to</p>

<p><a href="https://jfg-mysql.blogspot.com/2026/07/best-practice-no-timestamp-nor-enum.html">MySQL Best Practice : not using date / time types, nor ENUM</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Java Connector 3.5.10, 3.4.4, 3.3.6, and 2.7.15 now available</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/mariadb-java-connector-3-5-10-3-4-4-3-3-6-and-2-7-15-now-available/" />
      <id>https://mariadb.com/resources/blog/mariadb-java-connector-3-5-10-3-4-4-3-3-6-and-2-7-15-now-available/</id>
      <updated>2026-07-29T21:34: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/J 3.5.10, 3.4.4, 3.3.6, and 2.7.15 releases. Release Notes […]</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-java-connector-3-5-10-3-4-4-3-3-6-and-2-7-15-now-available/">MariaDB Java Connector 3.5.10, 3.4.4, 3.3.6, and 2.7.15 now available</a> appeared first on <a 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.10, 3.4.4, 3.3.6, and 2.7.15 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-10-3-4-4-3-3-6-and-2-7-15-now-available/" rel="nofollow">Source</a></p>

<p><a href="https://mariadb.com/resources/blog/mariadb-java-connector-3-5-10-3-4-4-3-3-6-and-2-7-15-now-available/">MariaDB Java Connector 3.5.10, 3.4.4, 3.3.6, and 2.7.15 now available</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>From a Production Problem to MariaDB: Headout’s Open-Source Contribution Journey</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/from-a-production-problem-to-mariadb-headouts-open-source-contribution-journey/" />
      <id>https://mariadb.org/from-a-production-problem-to-mariadb-headouts-open-source-contribution-journey/</id>
      <updated>2026-07-29T07:01:00+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>A production bottleneck at Headout led to a new MariaDB Server improvement. This is the story of how engineers, maintainers and AI-assisted development turned a real-world problem into an upstream open-source contribution.<br />
The post From a Production Problem to MariaDB: Headout’s Open-Source Contribution Journey appeared first on MariaDB.org.</p>
<p><a href="https://mariadb.org/from-a-production-problem-to-mariadb-headouts-open-source-contribution-journey/">From a Production Problem to MariaDB: Headout’s Open-Source Contribution Journey</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>A production bottleneck at Headout led to a new MariaDB Server improvement. This is the story of how engineers, maintainers and AI-assisted development turned a real-world problem into an upstream open-source contribution.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/from-a-production-problem-to-mariadb-headouts-open-source-contribution-journey/">From a Production Problem to MariaDB: Headout&rsquo;s Open-Source Contribution Journey</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p><a href="https://mariadb.org/from-a-production-problem-to-mariadb-headouts-open-source-contribution-journey/">From a Production Problem to MariaDB: Headout’s Open-Source Contribution Journey</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB 12.3: Faster Vector Search with Matryoshka Optimization</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/mariadb-12-3-faster-vector-search-with-matryoshka-optimization/" />
      <id>https://mariadb.com/resources/blog/mariadb-12-3-faster-vector-search-with-matryoshka-optimization/</id>
      <updated>2026-07-28T18:29:55+00:00</updated>
      <author><name>Egor Ustinov</name></author>
      <summary type="html"><![CDATA[<p>Achieving Fast Vector Search in MariaDB MariaDB Server 12.3 makes vector search faster where it matters most: the high-recall levels […]</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-12-3-faster-vector-search-with-matryoshka-optimization/">MariaDB 12.3: Faster Vector Search with Matryoshka Optimization</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB Server 12.3 makes vector search faster where it matters most: the high-recall levels that production AI workloads actually require. A new Matryoshka-aware optimization uses a cheap check on the first slice of each embedding to discard far-away candidates, then confirms the close ones with the full vector &mdash; so search stays just as accurate while doing far less work.</p>
<p><a href="https://mariadb.com/resources/blog/mariadb-12-3-faster-vector-search-with-matryoshka-optimization/" rel="nofollow">Source</a></p>

<p><a href="https://mariadb.com/resources/blog/mariadb-12-3-faster-vector-search-with-matryoshka-optimization/">MariaDB 12.3: Faster Vector Search with Matryoshka Optimization</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Backups Using the MySQL Clone Operation</title>
      <link rel="alternate" type="text/html" href="https://www.fromdual.com/blog/backups-with-mysql-clone/" />
      <id>https://www.fromdual.com/blog/backups-with-mysql-clone/</id>
      <updated>2026-07-28T09:40:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>We recently tested the PostgreSQL backup tool pg_basebackup and were very impressed with its remote backup functionality, which allows for both physical local and physical remote backups.<br />
This led us to wonder whether a physical remote backup is also possible using the “new” MySQL Server Clone feature, which was introduced in MySQL 8.0.17 (July 2019).<br />
The MySQL Clone operation can be used to create both a local and a remote copy of the database. The original idea behind this feature was likely to automatically create nodes in an InnoDB Cluster (similar to Percona XtraDB Cluster SST).<br />
Terminology used in the clone operation:</p>
<p>Donor (source database)<br />
Recipient (destination database)</p>
<p>The clone operation is initiated from the recipient. The data can be cloned to the recipient’s own directory or, alternatively, to a different directory.<br />
Preparations<br />
The plugin must be installed on both the Donor and the Recipient.<br />
SQL > INSTALL PLUGIN clone SONAME \'mysql_clone.so\';<br />
Query OK, 0 rows affected (0.01 sec)</p>
<p>SQL > SELECT PLUGIN_NAME, PLUGIN_STATUS<br />
 FROM INFORMATION_SCHEMA.PLUGINS<br />
 WHERE PLUGIN_NAME = \'clone\'<br />
;<br />
+-------------+---------------+<br />
&#124; PLUGIN_NAME &#124; PLUGIN_STATUS &#124;<br />
+-------------+---------------+<br />
&#124; clone &#124; ACTIVE &#124;<br />
+-------------+---------------+<br />
If you want to force the plugin to load on restart, it must be configured as follows in the MySQL configuration file (my.cnf):<br />
[mysqld]<br />
plugin_load_add = mysql_clone.so<br />
clone = FORCE_PLUS_PERMANENT<br />
Local Backup Using Clone<br />
This method can serve as a replacement for a physical backup solution (xtrabackup or MySQL Enterprise Backup (mysql_backup)). On the database acting as the recipient in this case, execute the following command:<br />
SQL > CLONE LOCAL DATA DIRECTORY = \'/mnt/backup/mysql_clone\';<br />
The following items are still missing for the clone operation:</p>
<p>All TLS keys (*.pem files).<br />
The auto.cnf file, which contains the server_uuid.<br />
The mysqld-auto.cnf file, which contains dynamically modified, persistent server configuration variables.<br />
The mysql_upgrade_history.<br />
The MySQL configuration file (my.cnf) as well as<br />
The binary logs.</p>
<p>$ cp ${datadir}/*auto.cnf ${datadir}/mysql_upgrade_history ${datadir}/*.pem /mnt/backup/mysql_clone/<br />
Restoring the database is quite simple:<br />
$ systemctl stop mysql<br />
$ rm -rf ${datadir}/*<br />
$ cp -a /mnt/backup/mysql_clone/* ${datadir}/<br />
$ chown -R mysql: ${datadir}/*<br />
$ systemctl start mysql<br />
The #clone folder is created by the clone operation and can be ignored, but must not be deleted.<br />
$ ls -lad /mnt/backup/mysql_clone/*<br />
...<br />
drwxr-x--- 2 dba dba 4096 Jul 27 14:52 \'#clone\'<br />
...<br />
It is automatically removed when the MySQL database is started. If you delete it anyway, you will receive the following error messages:<br />
[System] [MY-013576] [InnoDB] InnoDB initialization has started.<br />
[System] [MY-013577] [InnoDB] InnoDB initialization has ended.<br />
mysqld: Can\'t create/write to file \'./performance_schema/clone_status_385.sdi\' (OS errno 2 - No such file or directory)<br />
mysqld: Can\'t create file \'./performance_schema/clone_status_385.sdi\' (errno: 2 - No such file or directory)<br />
[ERROR] [MY-013272] [Clone] Plugin Clone reported: \'Client: PFS table creation failed.\'<br />
[ERROR] [MY-010202] [Server] Plugin \'clone\' init function returned error.<br />
The binary log position required for point-in-time recovery can be determined as follows:<br />
SQL > SELECT BINLOG_FILE, BINLOG_POSITION FROM performance_schema.clone_status;<br />
+-------------------------------+-----------------+<br />
&#124; BINLOG_FILE &#124; BINLOG_POSITION &#124;<br />
+-------------------------------+-----------------+<br />
&#124; boss_percona-84_binlog.000003 &#124; 1231898 &#124;<br />
+-------------------------------+-----------------+<br />
Remote Backup Using Clone<br />
To create a remote backup using the clone functionality, a minimally functional MySQL database is required on the remote system. Unfortunately, a simple process or tool is not sufficient for this.<br />
On the donor server, you need a user with the following privileges:<br />
SQL > CREATE USER \'backup_user\'@\'%\' IDENTIFIED BY \'secret\';<br />
SQL > GRANT BACKUP_ADMIN ON *.* TO \'backup_user\'@\'%\';<br />
In addition, the potential donor must be specified on the recipient server:<br />
SQL > SET GLOBAL clone_valid_donor_list = \'192.168.1.129:3306\';<br />
The remote backup is then performed as follows:<br />
SQL > CLONE INSTANCE FROM \'backup_user\'@\'192.168.1.129\':3306 IDENTIFIED BY \'secret\'<br />
DATA DIRECTORY = \'/mnt/backup/mysql_clone\';<br />
The missing files described above must now also be copied somehow:<br />
$ scp mysql@192.168.1.129:${datadir}/*auto.cnf /mnt/backup/mysql_clone/<br />
$ scp mysql@192.168.1.129:${datadir}/mysql_upgrade_history /mnt/backup/mysql_clone/<br />
$ scp mysql@192.168.1.129:${datadir}/*.pem /mnt/backup/mysql_clone/<br />
Restoring the database is done in the same way as described above.<br />
Conclusion<br />
The MySQL Clone operation is a cool feature that I neglected for a long time because it never occurred to me that it could also be used for backup purposes.<br />
I wouldn’t be surprised if the MySQL developers took a cue from PostgreSQL’s pg_basebackup when they implemented this feature.<br />
Unfortunately, to my knowledge, this feature is still completely missing in MariaDB. Too bad!<br />
Sources</p>
<p>General: The Clone Plugin<br />
There are a few minor limitations for the clone backup, which are described here: Clone Plugin Limitations.<br />
Monitoring the clone backup is described here: Monitoring Cloning Operations.<br />
Tuning the clone backup is described here: Clone System Variable Reference</p>
<p><a href="https://www.fromdual.com/blog/backups-with-mysql-clone/">Backups Using the MySQL Clone Operation</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>We recently tested the PostgreSQL backup tool <code>pg_basebackup</code> and were very impressed with its remote backup functionality, which allows for both physical local and physical remote backups.</p>
<p>This led us to wonder whether a physical remote backup is also possible using the &ldquo;new&rdquo; MySQL Server Clone feature, which was introduced in MySQL 8.0.17 (July 2019).</p>
<p>The MySQL Clone operation can be used to create both a local and a remote copy of the database. The original idea behind this feature was likely to automatically create nodes in an InnoDB Cluster (similar to Percona XtraDB Cluster SST).</p>
<p>Terminology used in the clone operation:</p>
<ul>
<li>Donor (source database)</li>
<li>Recipient (destination database)</li>
</ul>
<p>The clone operation is initiated from the recipient. The data can be cloned to the recipient&rsquo;s own directory or, alternatively, to a different directory.</p>
<h2 id="preparations">Preparations<a class="anchor-link" id="preparations"></a></h2>
<p>The plugin must be installed on both the Donor and the Recipient.</p>
<pre><code>SQL&gt; INSTALL PLUGIN clone SONAME 'mysql_clone.so';
Query OK, 0 rows affected (0.01 sec)

SQL&gt; SELECT PLUGIN_NAME, PLUGIN_STATUS
 FROM INFORMATION_SCHEMA.PLUGINS
 WHERE PLUGIN_NAME = 'clone'
;
+-------------+---------------+
| PLUGIN_NAME | PLUGIN_STATUS |
+-------------+---------------+
| clone | ACTIVE |
+-------------+---------------+
</code></pre>
<p>If you want to force the plugin to load on restart, it must be configured as follows in the MySQL configuration file (<code>my.cnf</code>):</p>
<pre><code>[mysqld]
plugin_load_add = mysql_clone.so
clone = FORCE_PLUS_PERMANENT
</code></pre>
<h2 id="local-backup-using-clone">Local Backup Using Clone<a class="anchor-link" id="local-backup-using-clone"></a></h2>
<p>This method can serve as a replacement for a physical backup solution (<code>xtrabackup</code> or MySQL Enterprise Backup (<code>mysql_backup</code>)). On the database acting as the recipient in this case, execute the following command:</p>
<pre><code>SQL&gt; CLONE LOCAL DATA DIRECTORY = '/mnt/backup/mysql_clone';
</code></pre>
<p>The following items are still missing for the clone operation:</p>
<ul>
<li>All TLS keys (<code>*.pem</code> files).</li>
<li>The <code>auto.cnf</code> file, which contains the <code>server_uuid</code>.</li>
<li>The <code>mysqld-auto.cnf</code> file, which contains dynamically modified, persistent server configuration variables.</li>
<li>The <code>mysql_upgrade_history</code>.</li>
<li>The MySQL configuration file (<code>my.cnf</code>) as well as</li>
<li>The binary logs.</li>
</ul>
<pre><code>$ cp ${datadir}/*auto.cnf ${datadir}/mysql_upgrade_history ${datadir}/*.pem /mnt/backup/mysql_clone/
</code></pre>
<p>Restoring the database is quite simple:</p>
<pre><code>$ systemctl stop mysql
$ rm -rf ${datadir}/*
$ cp -a /mnt/backup/mysql_clone/* ${datadir}/
$ chown -R mysql: ${datadir}/*
$ systemctl start mysql
</code></pre>
<p>The <code>#clone</code> folder is created by the clone operation and can be ignored, but must not be deleted.</p>
<pre><code>$ ls -lad /mnt/backup/mysql_clone/*
...
drwxr-x--- 2 dba dba 4096 Jul 27 14:52 '#clone'
...
</code></pre>
<p>It is automatically removed when the MySQL database is started. If you delete it anyway, you will receive the following error messages:</p>
<pre><code>[System] [MY-013576] [InnoDB] InnoDB initialization has started.
[System] [MY-013577] [InnoDB] InnoDB initialization has ended.
mysqld: Can't create/write to file './performance_schema/clone_status_385.sdi' (OS errno 2 - No such file or directory)
mysqld: Can't create file './performance_schema/clone_status_385.sdi' (errno: 2 - No such file or directory)
[ERROR] [MY-013272] [Clone] Plugin Clone reported: 'Client: PFS table creation failed.'
[ERROR] [MY-010202] [Server] Plugin 'clone' init function returned error.
</code></pre>
<p>The binary log position required for point-in-time recovery can be determined as follows:</p>
<pre><code>SQL&gt; SELECT BINLOG_FILE, BINLOG_POSITION FROM performance_schema.clone_status;
+-------------------------------+-----------------+
| BINLOG_FILE | BINLOG_POSITION |
+-------------------------------+-----------------+
| boss_percona-84_binlog.000003 | 1231898 |
+-------------------------------+-----------------+
</code></pre>
<h2 id="remote-backup-using-clone">Remote Backup Using Clone<a class="anchor-link" id="remote-backup-using-clone"></a></h2>
<p>To create a remote backup using the clone functionality, a minimally functional MySQL database is required on the remote system. Unfortunately, a simple process or tool is not sufficient for this.</p>
<p>On the donor server, you need a user with the following privileges:</p>
<pre><code>SQL&gt; CREATE USER 'backup_user'@'%' IDENTIFIED BY 'secret';
SQL&gt; GRANT BACKUP_ADMIN ON *.* TO 'backup_user'@'%';
</code></pre>
<p>In addition, the potential donor must be specified on the recipient server:</p>
<pre><code>SQL&gt; SET GLOBAL clone_valid_donor_list = '192.168.1.129:3306';
</code></pre>
<p>The remote backup is then performed as follows:</p>
<pre><code>SQL&gt; CLONE INSTANCE FROM 'backup_user'@'192.168.1.129':3306 IDENTIFIED BY 'secret'
DATA DIRECTORY = '/mnt/backup/mysql_clone';
</code></pre>
<p>The missing files described above must now also be copied somehow:</p>
<pre><code>$ scp mysql@192.168.1.129:${datadir}/*auto.cnf /mnt/backup/mysql_clone/
$ scp mysql@192.168.1.129:${datadir}/mysql_upgrade_history /mnt/backup/mysql_clone/
$ scp mysql@192.168.1.129:${datadir}/*.pem /mnt/backup/mysql_clone/
</code></pre>
<p>Restoring the database is done in the same way as described above.</p>
<h2 id="conclusion">Conclusion<a class="anchor-link" id="conclusion"></a></h2>
<p>The MySQL Clone operation is a cool feature that I neglected for a long time because it never occurred to me that it could also be used for backup purposes.</p>
<p>I wouldn&rsquo;t be surprised if the MySQL developers took a cue from PostgreSQL&rsquo;s <code>pg_basebackup</code> when they implemented this feature.</p>
<p>Unfortunately, to my knowledge, this feature is still completely missing in MariaDB. Too bad!</p>
<h2 id="sources">Sources<a class="anchor-link" id="sources"></a></h2>
<ul>
<li>General: <a href="https://dev.mysql.com/doc/refman/9.7/en/clone-plugin.html" target="_blank" rel="noopener">The Clone Plugin</a></li>
<li>There are a few minor limitations for the clone backup, which are described here: <a href="https://dev.mysql.com/doc/refman/9.7/en/clone-plugin-limitations.html" target="_blank" rel="noopener">Clone Plugin Limitations</a>.</li>
<li>Monitoring the clone backup is described here: <a href="https://dev.mysql.com/doc/refman/9.7/en/clone-plugin-monitoring.html" target="_blank" rel="noopener">Monitoring Cloning Operations</a>.</li>
<li>Tuning the clone backup is described here: <a href="https://dev.mysql.com/doc/refman/9.7/en/clone-plugin-option-variable-reference.html" target="_blank" rel="noopener">Clone System Variable Reference</a></li>
</ul>

<p><a href="https://www.fromdual.com/blog/backups-with-mysql-clone/">Backups Using the MySQL Clone Operation</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Adobe Commerce Chooses MariaDB as Its Default Database Platform</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/adobe-commerce-chooses-mariadb-as-its-default-database-platform/" />
      <id>https://mariadb.org/adobe-commerce-chooses-mariadb-as-its-default-database-platform/</id>
      <updated>2026-07-27T12:49:48+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>Adobe Commerce is making MariaDB its default and recommended database<br />
platform.<br />
The post Adobe Commerce Chooses MariaDB as Its Default Database Platform appeared first on MariaDB.org.</p>
<p><a href="https://mariadb.org/adobe-commerce-chooses-mariadb-as-its-default-database-platform/">Adobe Commerce Chooses MariaDB as Its Default Database Platform</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Adobe Commerce is making MariaDB its default and recommended database<br>
platform. </p>
<p>The post <a rel="nofollow" href="https://mariadb.org/adobe-commerce-chooses-mariadb-as-its-default-database-platform/">Adobe Commerce Chooses MariaDB as Its Default Database Platform</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p><a href="https://mariadb.org/adobe-commerce-chooses-mariadb-as-its-default-database-platform/">Adobe Commerce Chooses MariaDB as Its Default Database Platform</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>ScalaHosting Becomes a Gold Sponsor of MariaDB Foundation</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/scalahosting-becomes-a-gold-sponsor-of-mariadb-foundation/" />
      <id>https://mariadb.org/scalahosting-becomes-a-gold-sponsor-of-mariadb-foundation/</id>
      <updated>2026-07-27T09:11:10+00:00</updated>
      <author><name>Anna Widenius</name></author>
      <summary type="html"><![CDATA[<p>Global cloud hosting provider supports the continued development and adoption of open-source MariaDB<br />
MariaDB Foundation is pleased to welcome ScalaHosting as a Gold Sponsor, strengthening the relationship between the MariaDB community and one of the hosting industry’s established cloud infrastructure providers. …<br />
Continue reading \"ScalaHosting Becomes a Gold Sponsor of MariaDB Foundation\"<br />
The post ScalaHosting Becomes a Gold Sponsor of MariaDB Foundation appeared first on MariaDB.org.</p>
<p><a href="https://mariadb.org/scalahosting-becomes-a-gold-sponsor-of-mariadb-foundation/">ScalaHosting Becomes a Gold Sponsor of MariaDB Foundation</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Global cloud hosting provider supports the continued development and adoption of open-source MariaDB<br>
MariaDB Foundation is pleased to welcome <a href="https://www.scalahosting.com/">ScalaHosting</a> as a <a href="https://mariadb.org/donate/#gold-tier-eur-50000-per-year">Gold Sponsor</a>, strengthening the relationship between the MariaDB community and one of the hosting industry&rsquo;s established cloud infrastructure providers. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/scalahosting-becomes-a-gold-sponsor-of-mariadb-foundation/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;ScalaHosting Becomes a Gold Sponsor of MariaDB Foundation&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/scalahosting-becomes-a-gold-sponsor-of-mariadb-foundation/">ScalaHosting Becomes a Gold Sponsor of MariaDB Foundation</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p><a href="https://mariadb.org/scalahosting-becomes-a-gold-sponsor-of-mariadb-foundation/">ScalaHosting Becomes a Gold Sponsor of MariaDB Foundation</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Introducing 🍃MClusterAdmin: A Lightweight GUI Tool for MongoDB DBAs</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/07/27/introducing-mclusteradmin-a-lightweight-gui-tool-for-mongodb-dba/" />
      <id>https://percona.community/blog/2026/07/27/introducing-mclusteradmin-a-lightweight-gui-tool-for-mongodb-dba/</id>
      <updated>2026-07-27T00:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>Over the years working on various MongoDB troubleshooting cases, I’ve been wondering how to handle the longish JSON outputs from the most common diagnostic commands, like rs.status(), sh.status(), or db.currentOp(), not to mention db.serverStatus()! They are just painful and slow to read. I even came up with various scripts to present the data in table format, similar to what we know from MySQL, but I was never satisfied with them.</p>
<p><a href="https://percona.community/blog/2026/07/27/introducing-mclusteradmin-a-lightweight-gui-tool-for-mongodb-dba/">Introducing 🍃MClusterAdmin: A Lightweight GUI Tool for MongoDB DBAs</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Over the years working on various MongoDB troubleshooting cases, I&rsquo;ve been wondering how to handle the longish JSON outputs from the most common diagnostic commands, like <code>rs.status()</code>, <code>sh.status()</code>, or <code>db.currentOp()</code>, not to mention <code>db.serverStatus()</code>! They are just painful and slow to read. I even came up with various scripts to present the data in <a href="https://github.com/kellyjonbrazil/jtbl" target="_blank" rel="noopener noreferrer">table format</a>, similar to what we know from MySQL, but I was never satisfied with them.</p>
<p>Finally, I thought it was futile to look for a universal solution that would yield human-friendly outputs in command-line sessions.</p>
<p>So, why not have a nice graphical interface to visualize replica sets, sharding state, connections, and more instead? I looked for available GUI tools for MongoDB and came to the impression that <strong>almost all of them are built</strong> <strong>for developers</strong>. They are great for browsing collections, editing documents, or composing queries, but when it comes to typical <strong>DBA</strong> daily tasks &mdash; checking replication health, inspecting replica set configuration, or understanding what is really going on inside a sharded cluster &mdash; we are really down to the mongo shell. There is nothing wrong with the shell client, of course, but some things are simply easier to digest when presented visually. Here, of course, <a href="https://docs.percona.com/percona-monitoring-and-management/3/install-pmm/install-pmm-client/connect-database/mongodb.html" target="_blank" rel="noopener noreferrer">PMM</a> does allow that, but it has a bit of a different purpose &ndash; long-term monitoring, and has to be installed and set up first. Besides, it does not do everything I wanted.</p>
<p>So, encouraged to experiment with vibe coding in Percona, I decided to experiment with filling this gap myself and started a small personal project: a lightweight <strong>DBA-oriented</strong> GUI interface called <strong>MClusterAdmin</strong>.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/07/mclusteradmin-picture.jpg" alt="MClusterAdmin"></figure>
</p>
<h3 id="project-description">Project description<a class="anchor-link" id="project-description"></a></h3>
<p>MClusterAdmin is a tool designed to be self-hosted, with a backend written in Go, while the frontend is in HTML and JavaScript. It offers a web UI interface featuring typical <strong>DBA perspective</strong> dashboards. The whole application ships as a single, small binary, with <strong>no agents</strong>, no external services, and no internal database of its own.</p>
<p>In order to try it, you need to copy the binary to a host that has access to the MongoDB servers to be monitored. I suggest running first locally on some test environment, like one created using <a href="https://github.com/PrzemekMalkowski/mlaunch-go" target="_blank" rel="noopener noreferrer">mlaunch</a>, <a href="https://github.com/zelmario/anydbver" target="_blank" rel="noopener noreferrer">anydbver</a>, <a href="https://github.com/percona/mongo_terraform_ansible" target="_blank" rel="noopener noreferrer">mongo_terraform_ansible</a>, or a similar sandbox tool. For <strong>Docker</strong> environments, you can use the already existing <a href="https://github.com/PrzemekMalkowski/mclusteradmin/pkgs/container/mclusteradmin" target="_blank" rel="noopener noreferrer">image</a>, just make sure your container shares the same network as the MongoDB cluster.</p>
<p>The tool has no authentication on its own. Very similarly to mongosh, the MongoDB URI and credentials you provide determine what it will be able to offer. You can connect a standalone instance, a replica set member, or a mongos router. The tool will discover the rest of the topology automatically and will establish individual connections to each member. No SSH access to the database hosts is needed.</p>
<p>With its tiny footprint, it should fit well into your cloud/K8S MongoDB deployments.</p>
<p>Let me be clear about what MClusterAdmin is <strong>not</strong>: it is not a data browser &mdash; you will not view or edit your collections&rsquo; documents with it. There are plenty of other tools for that! It is, again, not a long-term monitoring or alerting solution; for that, I strongly recommend <a href="https://docs.percona.com/percona-monitoring-and-management/3/" target="_blank" rel="noopener noreferrer">Percona Monitoring and Management (PMM)</a>. MClusterAdmin is designed for quick, interactive cluster inspection and simple administrative operations that a DBA performs many times a day.</p>
<p>I will point out the key features that I decided to implement further below, but I guess watching a quick <strong>demo presentation</strong> will allow you to judge the tool much faster:</p>
<div class="youtube__block">
<p>Link: <a href="https://youtu.be/RecQ7wtEV9g" class="youtube__link" target="_blank" rel="noopener">https://youtu.be/RecQ7wtEV9g</a></p>
</div>
<p>I don&rsquo;t think it makes sense to describe all the features I was able to implement so far in detail. I hope everything is intuitive enough so that you can see for yourself. The <a href="https://github.com/PrzemekMalkowski/mclusteradmin/blob/master/README.md" target="_blank" rel="noopener noreferrer">documentation</a> is available on GitHub if needed.</p>
<p>In short, my aim was to provide useful views covering <strong>replication topology</strong> and settings, sharding members and routers, but also overall database size statistics and their distribution among shards. This includes per-collection detailed sharding stats, data, and index sizes, etc. In addition to that, for each discovered MongoDB instance, you should be able to see basic WiredTiger usage stats, oplog details, including an on-demand breakdown of <strong>which collections changes generated the most recent oplog traffic</strong>, as well as some other most important server settings and usage summaries.</p>
<p>You will also find slow queries profiling, with the query explain module, as well as a quite functional users and roles management section.</p>
<p>The last <strong>security-focused</strong> section allows you to check whether authentication is enabled on each host, as well as what is the usage of each authentication mechanism.</p>
<h3 id="getting-started">Getting Started<a class="anchor-link" id="getting-started"></a></h3>
<p>Running MClusterAdmin takes a few seconds. Just download the suitable binary from the <a href="https://github.com/PrzemekMalkowski/mclusteradmin/releases" target="_blank" rel="noopener noreferrer">release page</a>, or compile it yourself:</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-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-shell" data-lang="shell"><span class="line"><span class="cl">git clone https://github.com/PrzemekMalkowski/mclusteradmin.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> mclusteradmin
</span></span><span class="line"><span class="cl">go build -o mca .
</span></span><span class="line"><span class="cl">./mca</span></span></code></pre>
</div>
</div>
</div>
<p>Then open the relevant address (by default http://localhost:8787) in your browser and paste your MongoDB connection URI to connect. There is also a <code>--view-only</code> flag, which disables all mutating operations both in the UI and at the API level &mdash; handy when you just want a safe, read-only window into a cluster. TLS mode for the web service interface is supported as well.</p>
<h3 id="kubernetes-integration">Kubernetes integration<a class="anchor-link" id="kubernetes-integration"></a></h3>
<p>It is extremely easy to add MClusterAdmin as a diagnostic pod to your existing MongoDB cluster in Kubernetes. Just use the available <a href="https://github.com/PrzemekMalkowski/mclusteradmin/pkgs/container/mclusteradmin" target="_blank" rel="noopener noreferrer">Docker image</a> from the GitHub repository!</p>
<p>An example MClusterAdmin.yaml configuration can be as simple as:</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">apiVersion: apps/v1
</span></span><span class="line"><span class="cl">kind: Deployment
</span></span><span class="line"><span class="cl">metadata:
</span></span><span class="line"><span class="cl"> name: MClusterAdmin
</span></span><span class="line"><span class="cl">spec:
</span></span><span class="line"><span class="cl"> replicas: 1
</span></span><span class="line"><span class="cl"> selector:
</span></span><span class="line"><span class="cl"> matchLabels:
</span></span><span class="line"><span class="cl"> app: MClusterAdmin
</span></span><span class="line"><span class="cl"> template:
</span></span><span class="line"><span class="cl"> metadata:
</span></span><span class="line"><span class="cl"> labels:
</span></span><span class="line"><span class="cl"> app: MClusterAdmin
</span></span><span class="line"><span class="cl"> spec:
</span></span><span class="line"><span class="cl"> containers:
</span></span><span class="line"><span class="cl"> - name: MClusterAdmin
</span></span><span class="line"><span class="cl"> image: ghcr.io/przemekmalkowski/mclusteradmin:latest
</span></span><span class="line"><span class="cl"> ports:
</span></span><span class="line"><span class="cl"> - containerPort: 8787</span></span></code></pre>
</div>
</div>
</div>
<h3 id="a-word-of-caution">A Word of Caution<a class="anchor-link" id="a-word-of-caution"></a></h3>
<p>MClusterAdmin is currently in <strong>beta</strong>, and it is a personal side project, so please do not point it at your production clusters just yet &mdash; test it in a safe environment first. The connected MongoDB user needs privileges matching the dashboards you want to use; the documentation breaks these down per feature, so you can follow the least-privilege approach instead of simply granting root.</p>
<h3 id="summary">Summary<a class="anchor-link" id="summary"></a></h3>
<p>MClusterAdmin was created as an attempt to fill the gap I found to be the case in the MongoDB community: a lightweight, replication- and sharding-aware GUI for MongoDB <strong>DBAs</strong>, free from any data-browsing ballast. The project is open source (GPLv3), and the code is available on <a href="https://github.com/PrzemekMalkowski/mclusteradmin" target="_blank" rel="noopener noreferrer">GitHub</a>. If you find it useful, miss a feature, or hit a bug, I would love to hear from you. Feedback is very welcome!</p>
<p><em>The article was created by a human.</em></p>

<p><a href="https://percona.community/blog/2026/07/27/introducing-mclusteradmin-a-lightweight-gui-tool-for-mongodb-dba/">Introducing 🍃MClusterAdmin: A Lightweight GUI Tool for MongoDB DBAs</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>The Queen and the “Half That Wasn’t Told”</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/the-queen-and-the-half-that-wasnt-told/" />
      <id>https://mariadb.org/the-queen-and-the-half-that-wasnt-told/</id>
      <updated>2026-07-26T18:55:29+00:00</updated>
      <author><name>Anna Widenius</name></author>
      <summary type="html"><![CDATA[<p>The Queen of Sheba did not travel lightly.<br />
She arrived in Jerusalem with difficult questions, a large caravan, camels carrying spices, and an impressive quantity of gold. …<br />
Continue reading \"The Queen and the “Half That Wasn’t Told”\"<br />
The post The Queen and the “Half That Wasn’t Told” appeared first on MariaDB.org.</p>
<p><a href="https://mariadb.org/the-queen-and-the-half-that-wasnt-told/">The Queen and the “Half That Wasn’t Told”</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>The Queen of Sheba did not travel lightly.<br>
She arrived in Jerusalem with difficult questions, a large caravan, camels carrying spices, and an impressive quantity of gold. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/the-queen-and-the-half-that-wasnt-told/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;The Queen and the &ldquo;Half That Wasn&rsquo;t Told&rdquo;&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/the-queen-and-the-half-that-wasnt-told/">The Queen and the &ldquo;Half That Wasn&rsquo;t Told&rdquo;</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p><a href="https://mariadb.org/the-queen-and-the-half-that-wasnt-told/">The Queen and the “Half That Wasn’t Told”</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>IBM continues as a Platinum Sponsor of MariaDB Foundation</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/ibm-continues-as-a-platinum-sponsor-of-mariadb-foundation/" />
      <id>https://mariadb.org/ibm-continues-as-a-platinum-sponsor-of-mariadb-foundation/</id>
      <updated>2026-07-24T13:12:09+00:00</updated>
      <author><name>Anna Widenius</name></author>
      <summary type="html"><![CDATA[<p>We are delighted to announce that IBM is continuing its support of MariaDB Foundation as a Platinum Sponsor.<br />
IBM’s sponsorship brings together two major enterprise computing platforms, IBM® …<br />
Continue reading \"IBM continues as a Platinum Sponsor of MariaDB Foundation\"<br />
The post IBM continues as a Platinum Sponsor of MariaDB Foundation appeared first on MariaDB.org.</p>
<p><a href="https://mariadb.org/ibm-continues-as-a-platinum-sponsor-of-mariadb-foundation/">IBM continues as a Platinum Sponsor of MariaDB Foundation</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>We are delighted to announce that IBM is continuing its support of MariaDB Foundation as a <a href="https://mariadb.org/donate/#platinum-tier-eur-100000-per-year">Platinum Sponsor</a>.<br>
IBM&rsquo;s sponsorship brings together two major enterprise computing platforms, IBM&reg; &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/ibm-continues-as-a-platinum-sponsor-of-mariadb-foundation/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;IBM continues as a Platinum Sponsor of MariaDB Foundation&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/ibm-continues-as-a-platinum-sponsor-of-mariadb-foundation/">IBM continues as a Platinum Sponsor of MariaDB Foundation</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p><a href="https://mariadb.org/ibm-continues-as-a-platinum-sponsor-of-mariadb-foundation/">IBM continues as a Platinum Sponsor of MariaDB Foundation</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Say the Name: MariaDB, MySQL, and the Ecosystem We Share</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/say-the-name-mariadb-mysql-and-the-ecosystem-we-share/" />
      <id>https://mariadb.org/say-the-name-mariadb-mysql-and-the-ecosystem-we-share/</id>
      <updated>2026-07-24T06:58:58+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>MariaDB and MySQL share history, tools, protocols and a large technical community. But compatibility does not mean identity—especially when reporting bugs. A MariaDB Connector/C issue submitted to the MySQL bug tracker offers a funny reminder to say the product’s actual name.<br />
The post Say the Name: MariaDB, MySQL, and the Ecosystem We Share appeared first on MariaDB.org.</p>
<p><a href="https://mariadb.org/say-the-name-mariadb-mysql-and-the-ecosystem-we-share/">Say the Name: MariaDB, MySQL, and the Ecosystem We Share</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>MariaDB and MySQL share history, tools, protocols and a large technical community. But compatibility does not mean identity&mdash;especially when reporting bugs. A MariaDB Connector/C issue submitted to the MySQL bug tracker offers a funny reminder to say the product&rsquo;s actual name.</p>
<p>The post <a rel="nofollow" href="https://mariadb.org/say-the-name-mariadb-mysql-and-the-ecosystem-we-share/">Say the Name: MariaDB, MySQL, and the Ecosystem We Share</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p><a href="https://mariadb.org/say-the-name-mariadb-mysql-and-the-ecosystem-we-share/">Say the Name: MariaDB, MySQL, and the Ecosystem We Share</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Percona Operator for MongoDB 1.23.0: ClusterSync Migration, Vector Search, and PVC Snapshot Backups</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/percona-operator-for-mongodb-1-23-0-clustersync-vector-search-pvc-snapshot-backups/" />
      <id>https://www.percona.com/blog/percona-operator-for-mongodb-1-23-0-clustersync-vector-search-pvc-snapshot-backups/</id>
      <updated>2026-07-23T19:55:51+00:00</updated>
      <author><name>Slava Sarzhan</name></author>
      <summary type="html"><![CDATA[<p>Percona Operator for MongoDB 1.23.0 makes the operator a place you move to, not just a place you start. A new ClusterSync component clones a live source and follows its change streams, so leaving a hosted service is a short cutover rather than a long outage. Alongside it, this release adds semantic vector search and … Continued<br />
The post Percona Operator for MongoDB 1.23.0: ClusterSync Migration, Vector Search, and PVC Snapshot Backups appeared first on Percona.</p>
<p><a href="https://www.percona.com/blog/percona-operator-for-mongodb-1-23-0-clustersync-vector-search-pvc-snapshot-backups/">Percona Operator for MongoDB 1.23.0: ClusterSync Migration, Vector Search, and PVC Snapshot Backups</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><img loading="lazy" decoding="async" class="aligncenter wp-image-50444 size-full" src="https://www.percona.com/wp-content/uploads/2026/07/Cover-1000-x-420.png" alt="" width="1001" height="420"><br>
<b>Percona Operator for MongoDB 1.23.0</b> makes the operator a place you move to, not just a place you start. A new ClusterSync component clones a live source and follows its change streams, so leaving a hosted service is a short cutover rather than a long outage. Alongside it, this release adds semantic vector search and storage-layer snapshot backups, two features that matter most once the data is yours to run.</p>
<p><span style="font-weight: 400">The three headline features are </span><b>Percona ClusterSync for MongoDB</b><span style="font-weight: 400">, </span><b>vector search</b><span style="font-weight: 400">, and </span><b>PVC snapshot backups</b><span style="font-weight: 400">. ClusterSync clones and continuously replicates a live source into an operator-managed cluster. Vector search brings semantic queries to Percona Server for MongoDB. PVC snapshot backups move backups off the network path and onto the storage layer.</span></p>
<p><span style="font-weight: 400">This release also widens where you can run it, adding official Rancher Kubernetes Engine (RKE2) support and full ARM64 images. Much of what shipped here traces back to requests on </span><a href="https://forums.percona.com/"><span style="font-weight: 400">forums.percona.com</span></a><span style="font-weight: 400"> and the public issue tracker.</span></p>
<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">ClusterSync migration and replication</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Vector search for semantic queries</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">PVC snapshot backups</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Other improvements worth knowing about<br>
</span></li>
</ul>
<p>&nbsp;</p>
<h2><b>Zero-Downtime Migration with Percona ClusterSync</b><a class="anchor-link" id="zero-downtime-migration-with-percona-clustersync"></a></h2>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-50445 size-large" src="https://www.percona.com/wp-content/uploads/2026/07/clustersync-migration-1024x569.png" alt="" width="1024" height="569"></p>
<p><span style="font-weight: 400">Moving a live MongoDB database onto the operator has always been the awkward first step. Dump-and-restore needs a maintenance window sized to your data, and hand-built replication between a source and a target is fragile to set up and easy to get wrong. This release introduces <a href="https://docs.percona.com/percona-clustersync-for-mongodb/">Percona ClusterSync for MongoDB</a> (PCSM) as an operator-managed component, so the migration path is via a Kubernetes object rather than a runbook.</span></p>
<p>&nbsp;</p>
<h3><span style="font-weight: 400"><b>Why it matters</b><br>
</span><a class="anchor-link" id="why-it-matters"></a></h3>
<p><span style="font-weight: 400">The common case is </span><span style="font-weight: 400">migrating</span><span style="font-weight: 400"> a hosted MongoDB service, for example MongoDB Atlas, for an operator-managed Percona Server for MongoDB cluster you control end to end. A typical trigger in production is a hosted-service bill that climbs with the workload, or a compliance requirement to keep data inside your own VPC and region: a team running a user-profile store on Atlas points PCSM at it, lets the target catch up over a day or two while the application keeps serving from Atlas, then cuts over in a maintenance window measured in seconds. PCSM clones the existing data, then tracks ongoing changes through MongoDB change streams, so the target stays current while you validate it. When you are ready, you cut the application over during a short window rather than a long one. The same mechanism keeps a continuously updated replica for non-production use or a hybrid-cloud copy.<br>
<b><br>
</b></span></p>
<h3><span style="font-weight: 400"><b>How it works</b><br>
</span><a class="anchor-link" id="how-it-works"></a></h3>
<p><span style="font-weight: 400">PCSM runs as its own container, deployed and managed through a new </span><em><span style="font-weight: 400">PerconaServerMongoDBClusterSync</span></em><span style="font-weight: 400"> custom resource. It performs an initial clone from the source connection string, then consumes change stream events to apply subsequent writes to the target. A </span><span style="font-weight: 400">mode</span><span style="font-weight: 400"> field controls the lifecycle: </span><span style="font-weight: 400">running</span><span style="font-weight: 400"> starts or resumes replication, </span><span style="font-weight: 400">paused</span><span style="font-weight: 400"> holds it, and </span><span style="font-weight: 400">finalized</span><span style="font-weight: 400"> stops replication.</span></p>
<p>&nbsp;</p>
<h3><b>Wiring it up</b><a class="anchor-link" id="wiring-it-up"></a></h3>

<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: psmdb.percona.com/v1
kind: PerconaServerMongoDBClusterSync
metadata:
  name: my-cluster-sync
spec:
  clusterName: my-target-cluster-name
  image: percona/percona-clustersync-mongodb:0.9.0
  # mode controls the PCSM lifecycle intent. Allowed values:
  #   running   - start/resume replication (default)
  #   paused    - pause an active replication
  #   finalized - stop replication
  mode: running
  source:
    uri: mongodb://source-cluster-mongos.source-namespace.svc.cluster.local:27017
    credentialsSecret: my-cluster-sync-source
  # excludeNamespaces lists MongoDB namespaces (db or db.collection) to skip.
  # excludeNamespaces:
  #   - admin
  #   - local</pre>
<p><em><span style="font-weight: 400">clusterName</span></em><span style="font-weight: 400"> names the operator-managed target that receives the data. </span><em><span style="font-weight: 400">source.uri</span></em><span style="font-weight: 400"> and </span><em><span style="font-weight: 400">source.credentialsSecret</span></em><span style="font-weight: 400"> point at the database you are migrating from, which can be Atlas, a self-managed replica set, or another operator cluster. </span><em><span style="font-weight: 400">mode</span></em><span style="font-weight: 400"> is the control you drive the cutover with: run to catch up, pause to hold, then finalize once the application points at the new cluster. The optional </span><em><span style="font-weight: 400">excludeNamespaces</span></em><span style="font-weight: 400"> list skips databases or collections you do not want to copy.</span><br>
&nbsp;</p>
<h3><b>Cutover and rollback</b><a class="anchor-link" id="cutover-and-rollback"></a></h3>
<p><span style="font-weight: 400">The cutover is yours to time, not the operator&rsquo;s. </span><span style="font-weight: 400">During the running replication</span><span style="font-weight: 400">, the target trails the source by the change-stream lag, which you watch until it is small and steady. You then stop writes on the source, let the last events drain, and repoint the application at the target cluster. Because the source keeps serving until you move the application, a rollback before cutover is simply leaving the application where it is. After cutover, treat the move as one-way once writes flow to the target, so verify the target thoroughly during the sync window rather than after.</span></p>
<blockquote>
<p><b>Note:</b><span style="font-weight: 400"> The PCSM component ships at version 0.9.0 with this release. Test the full migration and cutover against a staging copy before you run it on production data, and keep the source available until you have verified the target.</span></p>
</blockquote>
<p>&nbsp;</p>
<h2><b>Vector search for semantic queries</b><a class="anchor-link" id="vector-search-for-semantic-queries"></a></h2>
<p><span style="font-weight: 400">Vector search retrieves results by meaning rather than exact keyword match, which is the retrieval pattern behind semantic search and retrieval-augmented generation for AI applications. Teams that already store their data in MongoDB have had to copy vectors into a separate engine to do this, which adds a system to run and a pipeline to keep in sync. In production, this is the pattern behind a support tool that surfaces past tickets describing the same problem in different words, a product catalog that returns items by intent rather than exact keywords, and a RAG service that grounds a model on internal documents. This release lets you store and query vector data alongside your regular documents in Percona Server for MongoDB, so those workloads query one system instead of two.</span><br>
&nbsp;</p>
<h3><b>How it works</b><a class="anchor-link" id="how-it-works"></a></h3>
<p><span style="font-weight: 400">The operator deploys and manages the </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400"> search process, wires its authentication and TLS to the rest of the cluster, and keeps the search index synchronized for both replica set and sharded deployments. Applications query the index through the same MongoDB connection they already use, so you add semantic search without a second client, a second driver, or a second set of credentials. You do not stand up or secure a separate search tier; the operator treats </span><span style="font-weight: 400">mongot</span><span style="font-weight: 400"> as another managed component of the cluster.</span><br>
&nbsp;</p>
<h3><b>Wiring it up</b><a class="anchor-link" id="wiring-it-up"></a></h3>
<p><span style="font-weight: 400">Enable the search component in the custom resource:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">spec:
  search:
    enabled: true
    image: perconalab/percona-server-mongodb-operator:main-mongot
    size: 1
    storage:
      persistentVolumeClaim:
        resources:
          requests:
            storage: 10Gi
    resources:
      requests:
        cpu: "2"
        memory: 2Gi</pre>
<p><em><span style="font-weight: 400">size</span></em><span style="font-weight: 400"> sets how many search nodes to run, and </span><em><span style="font-weight: 400">storage</span></em><span style="font-weight: 400"> gives the search index its own <em>PersistentVolumeClaim</em> so it does not compete with the database volume. Size the </span><em><span style="font-weight: 400">resources</span></em><span style="font-weight: 400"> block to your index: vector indexes are memory-sensitive, so give </span><em><span style="font-weight: 400">mongot</span></em><span style="font-weight: 400"> enough headroom for the corpus you intend to query.</span></p>
<blockquote>
<p><b>Note:</b><span style="font-weight: 400"> Vector search is a tech preview in 1.23.0 and is not recommended for production yet. It requires Percona Server for MongoDB 8.3 or later.</span></p>
</blockquote>
<p>&nbsp;</p>
<h2><b>PVC snapshot backups</b><a class="anchor-link" id="pvc-snapshot-backups"></a></h2>
<p><span style="font-weight: 400">Logical and streamed physical backups both push data across the network to object storage, and for a multi-terabyte cluster, that path is the bottleneck. Backups run long, restores run longer, and both compete with production traffic for CPU and bandwidth. This release adds backups built on PersistentVolumeClaim snapshots, </span><span style="font-weight: 400">which takes the storage layer directly</span><span style="font-weight: 400">.</span></p>
<p>&nbsp;</p>
<h3><b>Why it matters</b><a class="anchor-link" id="why-it-matters"></a></h3>
<p><span style="font-weight: 400">A PVC snapshot is a point-in-time copy of your data volumes taken at the storage layer through the Kubernetes VolumeSnapshot API. Because the operator asks the storage provider for a snapshot instead of streaming bytes out, a backup typically completes in seconds or minutes regardless of database size, and a restore is correspondingly fast. Two production situations show the difference: a nightly backup that no longer fits its window as a cluster grows past a few terabytes, and a staging refresh that ties up resources for hours while it restores a streamed copy. A storage-layer snapshot turns both into a near-instant operation. The speed comes from how the storage layer implements snapshots: instead of copying the whole volume, most backends record only the blocks that changed since the previous snapshot and reference the rest, so the cost tracks your change rate rather than the total database size. Snapshots also work with encrypted and TLS-enabled clusters, and they use fewer cluster resources because there is no long-running data-transfer job.</span></p>
<h3><a class="anchor-link" id=""></a></h3>
<p>&nbsp;</p>
<h3><b>Wiring it up</b><a class="anchor-link" id="wiring-it-up"></a></h3>
<p><span style="font-weight: 400">The operator takes snapshot backups in two ways: on demand through a </span><em><span style="font-weight: 400">PerconaServerMongoDBBackup</span></em><span style="font-weight: 400"> object, or on a schedule through a backup task. The scheduled form looks like this, using the </span><em><span style="font-weight: 400">external</span></em><span style="font-weight: 400"> type and a <em>VolumeSnapshotClass</em>:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">spec:
  backup:
    tasks:
      - name: daily-snapshot
        enabled: false
        schedule: "0 0 * * *"
        retention:
          count: 1
          type: count
          deleteFromStorage: true
        type: external
        volumeSnapshotClass: YOUR-VOLUME-SNAPSHOT-CLASS</pre>
<p><em><span style="font-weight: 400">type: external</span></em><span style="font-weight: 400"> tells the operator to take a storage-layer snapshot rather than stream a backup, and </span><span style="font-weight: 400">volumeSnapshotClass</span><span style="font-weight: 400"> names the <em>VolumeSnapshotClass</em> your CSI driver provides. The </span><em><span style="font-weight: 400">retention</span></em><span style="font-weight: 400"> block prunes old snapshots on the schedule you set. Your storage provider must support the Kubernetes VolumeSnapshot API for this to work.</span></p>
<p><span style="font-weight: 400">Snapshot backups complement the streamed and logical backups the operator already supports; they do not replace them. Snapshots usually live in the same storage account and region as the volumes they copy, so keep a streamed backup to object storage for off-site and cross-region disaster recovery. A practical policy pairs frequent fast snapshots for quick local recovery with a less frequent streamed backup for durability, and the operator runs both from the same </span><em><span style="font-weight: 400">backup.tasks</span></em><span style="font-weight: 400"> list.</span></p>
<blockquote>
<p><b><br>
Note: </b><span style="font-weight: 400">PVC snapshot backups are a tech preview in 1.23.0 and are not recommended for production yet. Snapshot portability and retention semantics depend on your CSI driver, so test restores before you rely on them.</span></p>
</blockquote>
<p>&nbsp;</p>
<h2><b>Other improvements</b><a class="anchor-link" id="other-improvements"></a></h2>
<p><span style="font-weight: 400">Beyond the three headline features, 1.23.0 ships a set of enhancements that smooth day-two operations:</span></p>
<ul>
<li style="font-weight: 400"><b>Operator-generated connection string Secrets </b><span style="font-weight: 400">(</span><a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1537"><span style="font-weight: 400">K8SPSMDB-1537</span></a><span style="font-weight: 400">): the operator now publishes a ready-to-use </span><b>MongoDB connection string </b><span style="font-weight: 400">(URI) in a Kubernetes Secret for the </span><i><span style="font-weight: 400">databaseAdmin</span></i><span style="font-weight: 400"> user. An application can read that one Secret and connect to it, instead of building the URI itself from Pod names, Services, TLS settings, and credentials.</span></li>
<li style="font-weight: 400"><b>Workload Identity for GCS backups</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1602"><span style="font-weight: 400">K8SPSMDB-1602</span></a><span style="font-weight: 400">): back up to Google Cloud Storage without storing a service-account JSON key in a Secret.</span></li>
<li style="font-weight: 400"><b>Oracle Cloud Infrastructure Object Storage</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1644"><span style="font-weight: 400">K8SPSMDB-1644</span></a><span style="font-weight: 400">) and </span><b>Alibaba Cloud OSS</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1519"><span style="font-weight: 400">K8SPSMDB-1519</span></a><span style="font-weight: 400">): two more native backup destinations.</span></li>
<li style="font-weight: 400"><b>Restore a collection under a different name</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1603"><span style="font-weight: 400">K8SPSMDB-1603</span></a><span style="font-weight: 400">): use selective.nsFrom and nsTo to restore one collection alongside the live one for inspection or recovery.</span></li>
<li style="font-weight: 400"><b>External nodes as arbiters</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1031"><span style="font-weight: 400">K8SPSMDB-1031</span></a><span style="font-weight: 400">): set </span><i><span style="font-weight: 400">arbiterOnly: true</span></i><span style="font-weight: 400"> on an external node to place a tie-breaker vote in a third location without a data-bearing member.</span></li>
<li style="font-weight: 400"><b>cert-manager ClusterIssuer and TLS policy </b><span style="font-weight: 400">(</span><a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1413"><span style="font-weight: 400">K8SPSMDB-1413</span></a><span style="font-weight: 400">, </span><a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1458"><span style="font-weight: 400">K8SPSMDB-1458</span></a><span style="font-weight: 400">): point the operator at an existing </span><i><span style="font-weight: 400">ClusterIssuer</span></i><span style="font-weight: 400">, and use </span><i><span style="font-weight: 400">certManagementPolicy</span></i><span style="font-weight: 400"> to keep certificate lifecycle fully under your control.</span></li>
<li style="font-weight: 400"><b>Tunable reconciliation interval </b><span style="font-weight: 400">(</span><a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1571"><span style="font-weight: 400">K8SPSMDB-1571</span></a><span style="font-weight: 400">): set </span><i><span style="font-weight: 400">RECONCILE_INTERVAL</span></i><span style="font-weight: 400"> to reduce Kubernetes API load on large fleets (default 5s).</span></li>
<li style="font-weight: 400"><b>Query Analytics via mongolog for PMM</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1546"><span style="font-weight: 400">K8SPSMDB-1546</span></a><span style="font-weight: 400">): choose mongolog as the QAN source in </span><a href="https://docs.percona.com/percona-monitoring-and-management/"><span style="font-weight: 400">Percona Monitoring and Management</span></a><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><b>Custom sidecar health probes</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1701"><span style="font-weight: 400">K8SPSMDB-1701</span></a><span style="font-weight: 400">, </span><a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1728"><span style="font-weight: 400">K8SPSMDB-1728</span></a><span style="font-weight: 400">) and </span><b>StatefulSet</b> <i><span style="font-weight: 400">revisionHistoryLimit</span></i><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPSMDB-1572"><span style="font-weight: 400">K8SPSMDB-1572</span></a><span style="font-weight: 400">): finer control over probes and rollout history.</span>&nbsp;</li>
</ul>
<p><span style="font-weight: 400">For the full list, including bug fixes, see the release notes linked below.</span></p>
<p>&nbsp;</p>
<h2><span style="font-weight: 400"><b>Conclusion</b></span><a class="anchor-link" id="conclusion"></a></h2>
<p><span style="font-weight: 400">Percona Operator for MongoDB 1.23.0 covers the arc from getting data in to keeping it safe: ClusterSync brings a live database onto the operator with a short cutover, vector search lets one system serve both documents and semantic queries, and PVC snapshot backups take the network out of the backup path. With RKE2 and full ARM64 support added, more of that runs on the platforms teams actually use. If there is a workflow you still script around the operator, tell us on the forum, since that is where releases like this one come from.<br>
</span></p>
<h2><span style="font-weight: 400"><br>
<b>Try Percona Operator for MongoDB 1.23.0<br>
</b></span><a class="anchor-link" id="try-percona-operator-for-mongodb-1-23-0"></a></h2>
<ul>
<li style="font-weight: 400"><b>Release notes</b><span style="font-weight: 400">: </span><a href="https://docs.percona.com/percona-operator-for-mongodb/RN/Kubernetes-Operator-for-PSMONGODB-RN1.23.0.html"><span style="font-weight: 400">Percona Operator for MongoDB 1.23.0 Release Notes</span></a></li>
<li style="font-weight: 400"><b>Documentation</b><span style="font-weight: 400">: </span><a href="https://docs.percona.com/percona-operator-for-mongodb/"><span style="font-weight: 400">Percona Operator for MongoDB docs</span></a></li>
<li style="font-weight: 400"><b>GitHub</b><span style="font-weight: 400">: </span><a href="https://github.com/percona/percona-server-mongodb-operator"><span style="font-weight: 400">percona/percona-server-mongodb-operator</span></a></li>
<li style="font-weight: 400"><b>Community Forum</b><span style="font-weight: 400">: </span><a href="https://forums.percona.com/"><span style="font-weight: 400">forums.percona.com</span></a><span style="font-weight: 400">: share your feedback, ask questions, or report issues</span></li>
</ul>
<h2><a class="anchor-link" id=""></a></h2>
<p>&nbsp;</p>
<p>The post <a href="https://www.percona.com/blog/percona-operator-for-mongodb-1-23-0-clustersync-vector-search-pvc-snapshot-backups/">Percona Operator for MongoDB 1.23.0: ClusterSync Migration, Vector Search, and PVC Snapshot Backups</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p><a href="https://www.percona.com/blog/percona-operator-for-mongodb-1-23-0-clustersync-vector-search-pvc-snapshot-backups/">Percona Operator for MongoDB 1.23.0: ClusterSync Migration, Vector Search, and PVC Snapshot Backups</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>How to Migrate from MySQL Galera Cluster to Percona XtraDB Cluster</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/migrate-mysql-galera-cluster-to-percona-xtradb-cluster/" />
      <id>https://www.percona.com/blog/migrate-mysql-galera-cluster-to-percona-xtradb-cluster/</id>
      <updated>2026-07-22T09:56:28+00:00</updated>
      <author><name>Dennis Kittrell</name></author>
      <summary type="html"><![CDATA[<p>On December 1, 2025, MariaDB announced that MySQL Galera Cluster will reach end of life on September 30, 2026. After that date, the MySQL build of Galera stops receiving maintenance and binary releases, and all new clustering features land only in MariaDB Galera Cluster. MariaDB’s recommended path is an in-place migration onto their own server. … Continued<br />
The post How to Migrate from MySQL Galera Cluster to Percona XtraDB Cluster appeared first on Percona.</p>
<p><a href="https://www.percona.com/blog/migrate-mysql-galera-cluster-to-percona-xtradb-cluster/">How to Migrate from MySQL Galera Cluster to Percona XtraDB Cluster</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>On December 1, 2025, <a href="https://mariadb.com/resources/blog/upgrade-now-announcing-mysql-galera-cluster-in-place-migration-to-mariadb-galera-cluster/" target="_blank" rel="noopener">MariaDB announced</a> that MySQL Galera Cluster will reach end of life on September 30, 2026. After that date, the MySQL build of Galera stops receiving maintenance and binary releases, and all new clustering features land only in MariaDB Galera Cluster. MariaDB&rsquo;s recommended path is an in-place migration onto their own server.</p>
<p>If you run MySQL Galera Cluster today, that gives you a real decision to make, and not much time to make it. The good news is that you have more than one option, and the one most teams overlook keeps you on MySQL.</p>
<h2>You have two paths, not one<a class="anchor-link" id="you-have-two-paths-not-one"></a></h2>
<p>The deadline forces a move, but it does not force you onto MariaDB. There are two realistic destinations, and the difference between them is larger than it first appears, because one is a database engine change and the other is not.</p>
<div>
<table style="width: 100%;border-collapse: collapse;font-size: 15px;line-height: 1.5;margin: 1em 0">
<thead>
<tr>
<th style="padding: 10px 14px;text-align: left;background: #6c3fd6;color: #ffffff;border: 1px solid #5a33b8"></th>
<th style="padding: 10px 14px;text-align: left;background: #6c3fd6;color: #ffffff;border: 1px solid #5a33b8;font-weight: 600">MariaDB Galera Cluster</th>
<th style="padding: 10px 14px;text-align: left;background: #6c3fd6;color: #ffffff;border: 1px solid #5a33b8;font-weight: 600">Percona XtraDB Cluster (PXC)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 10px 14px;vertical-align: top;background: #f0eef7;border: 1px solid #e4e4ea;font-weight: 600">Server</td>
<td style="padding: 10px 14px;vertical-align: top;background: #ffffff;border: 1px solid #e4e4ea">MariaDB, a hard fork of MySQL</td>
<td style="padding: 10px 14px;vertical-align: top;background: #f4f1fd;border: 1px solid #e4e4ea">Percona Server for MySQL, a drop-in compatible build of MySQL</td>
</tr>
<tr>
<td style="padding: 10px 14px;vertical-align: top;background: #f0eef7;border: 1px solid #e4e4ea;font-weight: 600">Nature of the move</td>
<td style="padding: 10px 14px;vertical-align: top;background: #ffffff;border: 1px solid #e4e4ea">Switch to a different database</td>
<td style="padding: 10px 14px;vertical-align: top;background: #f4f1fd;border: 1px solid #e4e4ea">Server and distribution change within MySQL ecosystem</td>
</tr>
<tr>
<td style="padding: 10px 14px;vertical-align: top;background: #f0eef7;border: 1px solid #e4e4ea;font-weight: 600">Relationship to MySQL</td>
<td style="padding: 10px 14px;vertical-align: top;background: #ffffff;border: 1px solid #e4e4ea">A separate database with its own behavior and dialect</td>
<td style="padding: 10px 14px;vertical-align: top;background: #f4f1fd;border: 1px solid #e4e4ea">The same MySQL you already run, kept compatible</td>
</tr>
<tr>
<td style="padding: 10px 14px;vertical-align: top;background: #f0eef7;border: 1px solid #e4e4ea;font-weight: 600">What changes when you migrate</td>
<td style="padding: 10px 14px;vertical-align: top;background: #ffffff;border: 1px solid #e4e4ea">New system tables, a different data dictionary, user accounts recreated by hand</td>
<td style="padding: 10px 14px;vertical-align: top;background: #f4f1fd;border: 1px solid #e4e4ea">Stays within the MySQL family; schema and accounts carry over</td>
</tr>
<tr>
<td style="padding: 10px 14px;vertical-align: top;background: #f0eef7;border: 1px solid #e4e4ea;font-weight: 600">Clustering</td>
<td style="padding: 10px 14px;vertical-align: top;background: #ffffff;border: 1px solid #e4e4ea">MariaDB Galera Cluster</td>
<td style="padding: 10px 14px;vertical-align: top;background: #f4f1fd;border: 1px solid #e4e4ea">Galera write-set replication on Percona&rsquo;s own open fork</td>
</tr>
<tr>
<td style="padding: 10px 14px;vertical-align: top;background: #f0eef7;border: 1px solid #e4e4ea;font-weight: 600">Ecosystem tooling</td>
<td style="padding: 10px 14px;vertical-align: top;background: #ffffff;border: 1px solid #e4e4ea">MariaDB&rsquo;s own backup and monitoring stack</td>
<td style="padding: 10px 14px;vertical-align: top;background: #f4f1fd;border: 1px solid #e4e4ea">Percona XtraBackup, Percona Monitoring and Management, Percona Toolkit</td>
</tr>
<tr>
<td style="padding: 10px 14px;vertical-align: top;background: #f0eef7;border: 1px solid #e4e4ea;font-weight: 600">Kubernetes</td>
<td style="padding: 10px 14px;vertical-align: top;background: #ffffff;border: 1px solid #e4e4ea">MariaDB&rsquo;s Kubernetes operator</td>
<td style="padding: 10px 14px;vertical-align: top;background: #f4f1fd;border: 1px solid #e4e4ea">Percona Operator for MySQL</td>
</tr>
<tr>
<td style="padding: 10px 14px;vertical-align: top;background: #f0eef7;border: 1px solid #e4e4ea;font-weight: 600">Support</td>
<td style="padding: 10px 14px;vertical-align: top;background: #ffffff;border: 1px solid #e4e4ea">MariaDB</td>
<td style="padding: 10px 14px;vertical-align: top;background: #f4f1fd;border: 1px solid #e4e4ea">Percona long-term support, no lock-in</td>
</tr>
</tbody>
</table>
</div>
<p>The pattern holds across every row. MariaDB Galera Cluster moves you to a different database and asks you to rebuild around it, while PXC keeps the database you already have and changes what sits underneath it. That is what makes the move below a distribution change rather than a re-platforming.</p>
<h2>&ldquo;In-place&rdquo; is still a database migration<a class="anchor-link" id="in-place-is-still-a-database-migration"></a></h2>
<p>MariaDB describes its path as near-zero downtime and in place. That is fair for the cluster mechanics, but it understates what is changing underneath. By MariaDB&rsquo;s own migration documentation, moving to MariaDB Galera Cluster means a different system table structure, a fundamentally different data dictionary, and user accounts and privileges that are not mapped one-to-one and must be recreated by hand.</p>
<p>In other words, you are not upgrading MySQL Galera Cluster. You are moving to a different database that also happens to use Galera. For many teams that is a larger project than the &ldquo;in-place&rdquo; label suggests, with application testing, account re-creation, and a new server to operate and support afterward.</p>
<h2>Why PXC is the natural landing spot<a class="anchor-link" id="why-pxc-is-the-natural-landing-spot"></a></h2>
<p>PXC treats this as continuity rather than conversion. It is MySQL, not a fork of it.</p>
<ul>
<li>It is built on Percona Server for MySQL, a drop-in compatible build of MySQL, so your schema, system tables, and user accounts carry over as they are.</li>
<li>Its clustering uses the same Galera write-set replication model you already run, on Percona&rsquo;s own open Galera fork, which we maintain and ship on our own schedule and on terms we control.</li>
<li>It keeps strong binary compatibility with MySQL and Percona Server for MySQL, and integrates with Percona XtraBackup, Percona Monitoring and Management, and both Kubernetes and traditional deployments.</li>
</ul>
<p>For a MySQL Galera Cluster user, that means the move is a server and distribution change inside the MySQL family, not a migration to a new database.</p>
<p>For a fuller version of this argument, see Marco Tusa&rsquo;s personal take: <a href="https://www.tusacentral.net/joomla/index.php/mysql-blogs/268-the-galera-crossroads-why-pxc-is-the-lifeline-for-mariadb-community-users" target="_blank" rel="noopener">The Galera Crossroads: Why PXC is the Lifeline for MariaDB Community Users</a>.</p>
<h2>Why PXC, not just the easier migration<a class="anchor-link" id="why-pxc-not-just-the-easier-migration"></a></h2>
<p>Staying on MySQL is the practical argument. There is also a case for choosing PXC on the merits, independent of how much migration effort each path takes.</p>
<ul>
<li><strong>It is genuinely MySQL, not a relative of it.</strong> PXC is Percona Server for MySQL, a drop-in compatible build of MySQL. MariaDB began as a MySQL fork but has diverged over the years and is no longer a drop-in replacement for MySQL. With PXC, your MySQL knowledge, queries, tooling, and application compatibility carry forward. With MariaDB, some of that has to be revisited.</li>
<li><strong>Open source, with nothing held back.</strong> Percona ships its software, including PXC and our Galera fork, as open source, with no enterprise-only tier gating the features you depend on. MariaDB operates as a commercial vendor, with proprietary and enterprise components alongside the community server. If freedom from lock-in is part of why you run open source databases, that difference matters.</li>
<li><strong>A steward with no competing database to sell you.</strong> This one is worth stating plainly. The company retiring the MySQL build of Galera is the same company recommending you move onto its own database. MariaDB owns Codership, the maintainer of Galera, and has set the end-of-life date while pointing those users to MariaDB Galera Cluster. Percona does not sell a competing database. Our interest is in keeping you successful on MySQL, which is the same interest you have.</li>
<li><strong>A long track record in the MySQL ecosystem.</strong> Percona has maintained MySQL-focused software for years, including Percona Server for MySQL, Percona XtraBackup, Percona Monitoring and Management, and Percona XtraDB Cluster, all under long-term support. Supporting MySQL users is not a new direction for us.</li>
</ul>
<p>Taken together, the question is not only which migration is easier. It is which project is built around keeping MySQL open, compatible, and independent, and which one benefits from MySQL Galera coming to an end.</p>
<h2>What the migration looks like<a class="anchor-link" id="what-the-migration-looks-like"></a></h2>
<p>Because PXC stays within the MySQL ecosystem, the migration is a distribution change rather than a re-platforming exercise, so the work is mostly planning, testing, and a controlled cutover.</p>
<p><strong>Before you start.</strong> Inventory your current cluster: the exact MySQL and Galera versions, node topology, wsrep settings, and any custom configuration. Confirm the PXC version that lines up with your MySQL version, so you are moving across a compatible boundary rather than changing major versions at the same time. Stand up a staging cluster that mirrors production, and capture a baseline backup with Percona XtraBackup before you touch anything.</p>
<p>The right approach depends mainly on how much downtime you can tolerate, ranging from a straightforward binary swap during a maintenance window to a near-online cutover for systems that must stay available. The full, step-by-step guide lives on <a href="http://docs.percona.com" target="_blank" rel="noopener">docs.percona.com</a>, where we keep it current as the tooling improves, and we will link it here once it is published.</p>
<p>Whichever path you take, the same disciplines apply: rehearse the whole thing on staging first, validate application behavior and query performance against PXC before production, and keep a tested rollback (a verified backup or an untouched source cluster) until you are confident. Plan any cutover for a low-traffic window and watch cluster and replication health closely for the first hours afterward.</p>
<h2>Start before the deadline<a class="anchor-link" id="start-before-the-deadline"></a></h2>
<p>September 30, 2026 is when maintenance and binary releases stop for MySQL Galera Cluster. Running an unmaintained cluster past that point means no security patches and no bug fixes, which is not where you want a mission-critical system to sit. The time it takes to test and validate a move now is worth far more than the risk of waiting.</p>
<p>If you are weighing your options, the short version is this: you do not have to leave MySQL to keep a supported, open source Galera cluster. PXC is here, it is maintained, and it is the closest thing to staying exactly where you are.</p>
<h2>Talk to us<a class="anchor-link" id="talk-to-us"></a></h2>
<p>If you want help mapping out a migration path, sizing the work, or pressure-testing your high availability strategy, reach out to your Percona contact, post in the <a href="https://forums.percona.com" target="_blank" rel="noopener">Percona community forums</a>, or connect with our team directly. We are happy to walk through it with you.</p>
<p>&nbsp;</p>
<hr>
<p><em>Written by Dennis Kittrell. Reviewed by Michal Nosek and Marco Tusa.</em></p>
<p><em>MySQL, MariaDB, and Galera Cluster are trademarks of their respective owners. Percona is not affiliated with, sponsored by, or endorsed by these owners.</em></p>
<p>The post <a href="https://www.percona.com/blog/migrate-mysql-galera-cluster-to-percona-xtradb-cluster/">How to Migrate from MySQL Galera Cluster to Percona XtraDB Cluster</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p><a href="https://www.percona.com/blog/migrate-mysql-galera-cluster-to-percona-xtradb-cluster/">How to Migrate from MySQL Galera Cluster to Percona XtraDB Cluster</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Deploying the MariaDB Privacy-First Stack Anywhere with Terraform</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/deploying-the-mariadb-privacy-first-stack-anywhere-with-terraform/" />
      <id>https://mariadb.org/deploying-the-mariadb-privacy-first-stack-anywhere-with-terraform/</id>
      <updated>2026-07-22T09:07:49+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>In my previous post, I introduced the MariaDB Privacy-First Stack.<br />
Nextcloud for collaboration, Passbolt for passwords and secrets, and MariaDB Server for the data. …<br />
Continue reading \"Deploying the MariaDB Privacy-First Stack Anywhere with Terraform\"<br />
The post Deploying the MariaDB Privacy-First Stack Anywhere with Terraform appeared first on MariaDB.org.</p>
<p><a href="https://mariadb.org/deploying-the-mariadb-privacy-first-stack-anywhere-with-terraform/">Deploying the MariaDB Privacy-First Stack Anywhere with Terraform</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>In my previous post, I introduced the <a href="https://mariadb.org/mariadb-privacy-first-stack-nextcloud-passbolt-and-mariadb-server/">MariaDB Privacy-First Stack</a>.<br>
<a href="https://nextcloud.com">Nextcloud</a> for collaboration, <a href="https://www.passbolt.com/">Passbolt</a> for passwords and secrets, and MariaDB Server for the data. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/deploying-the-mariadb-privacy-first-stack-anywhere-with-terraform/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;Deploying the MariaDB Privacy-First Stack Anywhere with Terraform&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/deploying-the-mariadb-privacy-first-stack-anywhere-with-terraform/">Deploying the MariaDB Privacy-First Stack Anywhere with Terraform</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p><a href="https://mariadb.org/deploying-the-mariadb-privacy-first-stack-anywhere-with-terraform/">Deploying the MariaDB Privacy-First Stack Anywhere with Terraform</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>From PostgreSQL 12 to MariaDB 11: A Gradual Fintech Migration with 23% Lower TCO</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/from-postgresql-12-to-mariadb-11-a-gradual-fintech-migration-with-23-lower-tco/" />
      <id>https://mariadb.org/from-postgresql-12-to-mariadb-11-a-gradual-fintech-migration-with-23-lower-tco/</id>
      <updated>2026-07-21T09:13:23+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>Database migrations are rarely only about replacing one database server with another.<br />
In real production systems, especially in fintech, a migration is usually about reducing risk, keeping the application online, improving scalability, and giving teams more room to evolve the architecture without freezing product development. …<br />
Continue reading \"From PostgreSQL 12 to MariaDB 11: A Gradual Fintech Migration with 23% Lower TCO\"<br />
The post From PostgreSQL 12 to MariaDB 11: A Gradual Fintech Migration with 23% Lower TCO appeared first on MariaDB.org.</p>
<p><a href="https://mariadb.org/from-postgresql-12-to-mariadb-11-a-gradual-fintech-migration-with-23-lower-tco/">From PostgreSQL 12 to MariaDB 11: A Gradual Fintech Migration with 23% Lower TCO</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Database migrations are rarely only about replacing one database server with another.<br>
In real production systems, especially in fintech, a migration is usually about reducing risk, keeping the application online, improving scalability, and giving teams more room to evolve the architecture without freezing product development. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/from-postgresql-12-to-mariadb-11-a-gradual-fintech-migration-with-23-lower-tco/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;From PostgreSQL 12 to MariaDB 11: A Gradual Fintech Migration with 23% Lower TCO&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/from-postgresql-12-to-mariadb-11-a-gradual-fintech-migration-with-23-lower-tco/">From PostgreSQL 12 to MariaDB 11: A Gradual Fintech Migration with 23% Lower TCO</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p><a href="https://mariadb.org/from-postgresql-12-to-mariadb-11-a-gradual-fintech-migration-with-23-lower-tco/">From PostgreSQL 12 to MariaDB 11: A Gradual Fintech Migration with 23% Lower TCO</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB 13.1 Feature in Focus: Validate Your Configuration Before Starting the Server</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-13-1-feature-in-focus-validate-your-configuration-before-starting-the-server/" />
      <id>https://mariadb.org/mariadb-13-1-feature-in-focus-validate-your-configuration-before-starting-the-server/</id>
      <updated>2026-07-20T10:54:10+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>Have you ever modified a MariaDB configuration file, restarted the service, and immediately regretted it?<br />
You wanted to change:<br />
but accidentally wrote:<br />
One missing letter.<br />
That is enough to turn a perfectly healthy database server into a service that refuses to start. …<br />
Continue reading \"MariaDB 13.1 Feature in Focus: Validate Your Configuration Before Starting the Server\"<br />
The post MariaDB 13.1 Feature in Focus: Validate Your Configuration Before Starting the Server appeared first on MariaDB.org.</p>
<p><a href="https://mariadb.org/mariadb-13-1-feature-in-focus-validate-your-configuration-before-starting-the-server/">MariaDB 13.1 Feature in Focus: Validate Your Configuration Before Starting the Server</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Have you ever modified a MariaDB configuration file, restarted the service, and immediately regretted it?<br>
You wanted to change:<br>
but accidentally wrote:<br>
One missing letter.<br>
That is enough to turn a perfectly healthy database server into a service that refuses to start. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-13-1-feature-in-focus-validate-your-configuration-before-starting-the-server/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB 13.1 Feature in Focus: Validate Your Configuration Before Starting the Server&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-13-1-feature-in-focus-validate-your-configuration-before-starting-the-server/">MariaDB 13.1 Feature in Focus: Validate Your Configuration Before Starting the Server</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p><a href="https://mariadb.org/mariadb-13-1-feature-in-focus-validate-your-configuration-before-starting-the-server/">MariaDB 13.1 Feature in Focus: Validate Your Configuration Before Starting the Server</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Using AI to modernize a Java project</title>
      <link rel="alternate" type="text/html" href="https://programmingbrain.com/2025/07/how-i-used-ai-coding-agents-to-modernize-a-java-library.html" />
      <id>https://programmingbrain.com/2025/07/how-i-used-ai-coding-agents-to-modernize-a-java-library.html</id>
      <updated>2026-07-20T08:05:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>How I used AI coding agents to modernize a Java library.</p>
<p><a href="https://programmingbrain.com/2025/07/how-i-used-ai-coding-agents-to-modernize-a-java-library.html">Using AI to modernize a Java project</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>How I used AI coding agents to modernize a Java library.</p>

<p><a href="https://programmingbrain.com/2025/07/how-i-used-ai-coding-agents-to-modernize-a-java-library.html">Using AI to modernize a Java project</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>TDE performance in PostgreSQL</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/07/20/tde-performance-in-postgresql/" />
      <id>https://percona.community/blog/2026/07/20/tde-performance-in-postgresql/</id>
      <updated>2026-07-20T00:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>What’s the impact of TDE on performance? People usually quickly throw together a few graphs with basic measurements and treat that as a complete answer, but the question is a bit more complex than that. In this blog post, I’ll try to explain it in a bit more detail: why showcasing a single graph isn’t good for anything other than marketing.</p>
<p><a href="https://percona.community/blog/2026/07/20/tde-performance-in-postgresql/">TDE performance in PostgreSQL</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>What&rsquo;s the impact of TDE on performance?<br>
People usually quickly throw together a few graphs with basic measurements and treat that as a complete answer, but the question is a bit more complex than that.<br>
In this blog post, I&rsquo;ll try to explain it in a bit more detail: why showcasing a single graph isn&rsquo;t good for anything other than marketing.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/07/pg_tde_superfast.png" alt="It&rsquo;s SUPER FAST!"></figure>
</p>
<h2 id="agenda">Agenda<a class="anchor-link" id="agenda"></a></h2>
<p>Let me start by making something clear: this is a complex topic, and this will be a long blog post.</p>
<p>I decided to simplify several things to the level where it is accurate enough, but still relatively easy to understand.<br>
I also won&rsquo;t go into implementation details, the maths behind statistics, and I won&rsquo;t focus on generic topics like how to do low-noise benchmarking.</p>
<p>I am already planning on writing separate blog posts about some of these details in the future, but if you are interested in some of them, don&rsquo;t hesitate to ask!<br>
Feedback like that helps me figure out what topic to cover next.</p>
<p>As for this blog post, I want to cover the following topics:</p>
<ul>
<li>A generic introduction about the cost of encryption on modern CPUs</li>
<li>A short description of how transparent data-at-rest encryption typically integrates into PostgreSQL</li>
<li>An explanation of why relation encryption (encrypting the database objects) typically has no cost at all in most workloads, except a few specific operations</li>
<li>And finally showcasing how the typical way of implementing WAL encryption in TDE solutions can cause performance degradation in high WAL-churn scenarios</li>
</ul>
<h2 id="test-setup">Test setup<a class="anchor-link" id="test-setup"></a></h2>
<p>Benchmarks heavily depend on the computer used for running them.<br>
All of my tests were performed on an AMD Threadripper 3970X (32 cores), using an Intel Optane P5800X SSD.</p>
<p>While some of the measurements can be reproduced on typical desktop hardware, not all of them can.<br>
Some tests require many parallel workers and high memory bandwidth.<br>
The more interesting tests all measure write performance, which is difficult on typical M.2 SSDs:<br>
while some of them have peak write performance similar to Optane disks, they can only keep up with high write speeds for short bursts, quickly turning an otherwise CPU or memory limited test into an IO limited one.</p>
<h2 id="encryption-is-cheap-in-isolation">Encryption is cheap&hellip; in isolation<a class="anchor-link" id="encryption-is-cheap-in-isolation"></a></h2>
<p>When talking about encryption performance, technical people usually make one of two assumptions:</p>
<ul>
<li>Encryption is complex math we have to compute, so of course it will degrade our performance!</li>
<li>We are using things like full filesystem encryption (BitLocker, LUKS), swap encryption or TLS all the time, it&rsquo;s barely noticeable &ndash; encryption is cheap!</li>
</ul>
<p>And both groups are kind of right:<br>
Encryption is complex math, and it will degrade performance on older hardware.<br>
But because it is so common and required for everything, modern hardware has a specialized instruction set (AES-NI) that highly optimizes it for most workloads.</p>
<p>The following table shows some single threaded measurements on my test computer, produced with small C benchmarks:</p>
<table>
<thead>
<tr>
<th>What</th>
<th>Bandwidth (GB/s)</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Memory read</td>
<td>23</td>
<td>How quickly can we read memory</td>
</tr>
<tr>
<td>Memory copy</td>
<td>11</td>
<td>How quickly can we copy data in memory from one place to another</td>
</tr>
<tr>
<td>Disk sequential read</td>
<td>7.4</td>
<td>How quickly can we read from disk</td>
</tr>
<tr>
<td>Disk sequential write</td>
<td>6.1</td>
<td>How quickly can we write to disk</td>
</tr>
<tr>
<td>AES-128-CTR</td>
<td>8.0</td>
<td>How quickly can we perform 128 bit AES CTR operations</td>
</tr>
<tr>
<td>AES-256-CTR</td>
<td>6.5</td>
<td>How quickly can we perform 256 bit AES CTR operations</td>
</tr>
<tr>
<td>AES-128-XTS</td>
<td>7.3</td>
<td>How quickly can we perform 128 bit AES XTS operations</td>
</tr>
<tr>
<td>AES-256-XTS</td>
<td>5.6</td>
<td>How quickly can we perform 256 bit AES XTS operations</td>
</tr>
<tr>
<td>AES-128-GCM</td>
<td>3.8</td>
<td>How quickly can we perform 128 bit AES GCM operations</td>
</tr>
<tr>
<td>AES-256-GCM</td>
<td>3.6</td>
<td>How quickly can we perform 256 bit AES GCM operations</td>
</tr>
</tbody>
</table>
<p>The three encryption modes mentioned above are commonly used in many scenarios:</p>
<ul>
<li>XTS is commonly used for full disk encryption, and also by some TDE implementations</li>
<li>CTR is commonly used by TDE implementations, especially for WAL encryption</li>
<li>GCM is an authenticated encryption algorithm which can provide both encryption and data integrity validation for TDE and other solutions</li>
</ul>
<p>While the table doesn&rsquo;t mention it, I also want to point out that all reads and writes are sequential using a 4kB block size.<br>
This is important, because the performance of all of them degrades if we start using them differently: if we start encrypting much smaller blocks at a time, or keep reinitializing the stream with different parameters, the encryption numbers can degrade quickly.</p>
<p>We also have to remember that our goal isn&rsquo;t to encrypt a random stream in memory &ndash; we have to integrate encryption into an existing database system with its own established architecture.<br>
The above numbers showcase our bandwidth to encrypt or decrypt data if a CPU core is only working on that task.<br>
In reality, the CPU will also be doing other things at the same time, and can&rsquo;t spend all the time on encryption.</p>
<p>However, this won&rsquo;t necessarily make things worse.<br>
Most database workloads are not CPU bound, the bottleneck is usually either disk or memory bandwidth.<br>
With AES-NI, encryption operations often don&rsquo;t require additional memory bandwidth, which means that if a workload is already disk or memory limited, but we have free CPU cycles, we might get encryption for free or at little cost.</p>
<p>The real question isn&rsquo;t how quick encryption is, but how optimally we can integrate it into PostgreSQL.</p>
<h2 id="what-are-we-encrypting-exactly">What are we encrypting exactly?<a class="anchor-link" id="what-are-we-encrypting-exactly"></a></h2>
<p>That means we no longer have a single question.<br>
I can&rsquo;t give a single answer to <em>how fast is pg_tde, or any other data-at-rest encryption implementation?</em></p>
<p>Because a database does many things, reads and writes many different file types, and each of those has to be implemented differently.</p>
<p>In the case of pg_tde, we have two main areas:</p>
<ul>
<li>the encryption of database (relation) files</li>
<li>and the encryption of the write ahead log</li>
</ul>
<p>Other TDE implementations might encrypt other files too, for example temporary files, but I want to focus on the two areas supported by pg_tde, as these are the most significant from a performance perspective.</p>
<p>So let&rsquo;s look into the details of these separately.</p>
<h2 id="the-relation-files">The relation files<a class="anchor-link" id="the-relation-files"></a></h2>
<p>The quick summary for those only interested in the numbers:<br>
the impact for this is very little &ndash; for most operations, in the very difficult to measure category.<br>
The only exception to this is a few single threaded write heavy workloads, such as <code>CREATE TABLE AS SELECT ...</code>, <code>VACUUM FULL</code>, an <code>UPDATE</code> that updates all or most rows, or an <code>ALTER TABLE</code> that rewrites the entire table. In these, we can measure a 5-30% performance drop.</p>
<p>For other operations, such as typical sysbench workloads, or even specific tests like single worker sequential reads, that number is 2% or less.<br>
Typical measurements usually have some noise, a few percent even for properly configured setups, and much more for <em>&ldquo;let&rsquo;s just quickly execute sysbench&rdquo;</em>.<br>
Something in the 1-2% range can only be measured with specific server and hardware configuration, not in real-world setups.</p>
<p>The reason behind this is quite simple:<br>
Relation file reads and writes usually happen in entire blocks, with an 8k default size for PostgreSQL &ndash; it is very similar to OS level file system encryption.</p>
<p>A typical data-at-rest encryption implementation usually operates at the IO level: it encrypts immediately before we write a dirty buffer to disk, and it decrypts immediately after we read something from disk.</p>
<p>Decryption only happens when we actually have to read from disk. If the requested pages are already in the shared buffers, they are already decrypted, so there&rsquo;s no effect there.<br>
Encryption only happens when we are writing to disk. In a workload that isn&rsquo;t very write-heavy, all writes happen in the checkpointer and background writer, and backend processes never perform page writes directly, so encryption won&rsquo;t affect the QPS numbers at all.<br>
Even if a workload is so write-heavy that the server has to move some of the writes to the backend process, most likely the backend isn&rsquo;t CPU-bound, since most database workloads are either memory or IO limited.<br>
Unless we are writing a huge volume of data, such as the examples mentioned above, encryption won&rsquo;t be noticeable at all in these scenarios.</p>
<p>This read-write behavior is similar to full file system encryption, where the OS caches behave similarly, unless we keep calling <code>fsync</code> explicitly for writes.</p>
<p>Because the effect of encryption is so little, we have to be really careful how we set up our test environment, even for the heavy write scenarios where we can notice a somewhat larger difference.<br>
It&rsquo;s very easy to get this wrong, and then just measure noise.</p>
<p>For example, let&rsquo;s say that we don&rsquo;t want to spend too much time on initializing the dataset, and we only generate a 20GB dataset.<br>
We also set the shared buffers to 16GB because our test PC has more than enough RAM.<br>
This seems reasonable, but the problem is, once the data is loaded into the shared buffers, it will be permanently decrypted there, and 80% of our dataset fits into the buffers.<br>
Most read operations won&rsquo;t have to go to the disk, they find the requested page in the buffers, so we are not measuring encryption performance for them.</p>
<p>We might decide to keep the small dataset, and just use a small number for shared buffers, but then we are moving away from a production-like setup: is it realistic to run PostgreSQL with 128MB shared buffers in a real environment?<br>
Can the reduced shared buffer size affect the performance in some other ways?</p>
<p>And then we have to think about similar questions for writes.</p>
<p>Regardless of what we do, as I mentioned above, this is basically the same use case as file system encryption, and we already know that that&rsquo;s fast.<br>
If we are testing an encryption implementation, and we can see a significant performance degradation with only data file encryption, we found a bug, and should report it.</p>
<h3 id="why-is-ctas-so-slow">Why is CTAS so slow?<a class="anchor-link" id="why-is-ctas-so-slow"></a></h3>
<p>At the beginning of the previous section, I mentioned a few examples where we <em>can</em> measure a difference.</p>
<p>In my tests, I see the following worst-case performance degradation with them:</p>
<table>
<thead>
<tr>
<th>Command</th>
<th>Encryption Overhead</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>CREATE TABLE AS SELECT</code></td>
<td>30%</td>
</tr>
<tr>
<td><code>ALTER TABLE</code> performing a full rewrite</td>
<td>15%</td>
</tr>
<tr>
<td><code>VACUUM FULL</code></td>
<td>10%</td>
</tr>
<tr>
<td><code>UPDATE</code> all rows</td>
<td>10%</td>
</tr>
</tbody>
</table>
<p>These are all <em>worst case</em> numbers I was able to produce. This doesn&rsquo;t mean that all full table UPDATE operations will take 10% longer.</p>
<p>Also, in the case of <code>UPDATE</code>, the 10% measurement includes the time of the <code>CHECKPOINT</code>, as depending on the exact server configuration, some or most of the writes still happen in the checkpointer and background writer.<br>
The actual execution time of the query in the backend process was the same with and without encryption in all of my <code>UPDATE</code> measurements, unless I intentionally misconfigure the server.</p>
<p>A CTAS that simply copies a table is the worst performer because it doesn&rsquo;t have to do anything else.<br>
The other operations all have to do something extra with the data, but if we just duplicate one database table, then we normally only perform IO:<br>
read a page, write the page, repeat.</p>
<p>Except in our case, we have to read a page, decrypt the page, encrypt the page, write the page.<br>
We have to call encryption operations twice for every page: with AES-128-CTR at 8 GB/s, decrypting and then encrypting each page halves the effective bandwidth to around 4 GB/s, well below the disk speeds in my test setup.<br>
A CTAS operation is normally IO limited, but because of this doubled encryption work, it becomes encryption limited with TDE, even with the AES-NI instruction set.</p>
<p>I also want to explicitly repeat an important note from the beginning:<br>
this result requires a sustained disk IO that is faster than the CPU&rsquo;s encryption speed.<br>
Consumer grade SSDs can&rsquo;t sustain such speeds for long workloads, only for short bursts.<br>
It is only possible to reproduce this measurement on them with small datasets.<br>
With larger tables, the test will be limited by disk IO, not encryption.</p>
<h2 id="the-write-ahead-log">The write ahead log<a class="anchor-link" id="the-write-ahead-log"></a></h2>
<p>This is where things get more interesting.<br>
In my <a href="https://percona.community/blog/2026/07/08/pg_tde-our-fork-is-temporary-our-commitment-to-open-tde-is-not/">previous tde related blog post</a>, I mentioned that we are working on some encryption benchmarking, and that was about WAL performance.<br>
Also, one of the main changes in the upcoming pg_tde 2.2.3 release will be a significant improvement in this area.</p>
<p>To start with a similar summary:</p>
<ul>
<li>in my most-performant benchmark prototype, I can&rsquo;t measure a significant degradation even in a <strong>worst-case scenario workload</strong></li>
<li>with pg_tde before 2.2.3, the same test results in only 60% throughput, or a 40%+ performance degradation</li>
<li>with pg_tde 2.2.3, we were able to improve that test to the 80% range, reducing the performance degradation to 20%</li>
</ul>
<p>The 2.2.3 change (already merged, the release is upcoming) improves our current WAL encryption approach. The complete fix described later in this post is still in development.</p>
<p>This list also needs two important footnotes:</p>
<p>First, this is a &ldquo;worst-case scenario&rdquo;, not something typically executed in production workloads.<br>
It is a synthetic scenario exactly to stress test WAL bottlenecks.<br>
In a simple OLTP read-write scenario, there&rsquo;s only a few percent measurable difference for all pg_tde versions, less than 10%.</p>
<p>Second, this is a concurrency issue, and reproducing it requires a high core count test PC.<br>
In my test setup, I have 32 cores available.<br>
On different hardware, the results will be different.<br>
With 100+ core monster hardware, and even more threads, the performance degradation is most likely even worse for most implementations of WAL encryption, but I didn&rsquo;t perform tests on such setups.</p>
<h3 id="understanding-the-issue">Understanding the issue<a class="anchor-link" id="understanding-the-issue"></a></h3>
<p>To understand why this is so different from data file encryption, we have to look into how WAL works:</p>
<ol>
<li>When we perform any WAL logged write, the changes are first written to WAL, that&rsquo;s why it&rsquo;s called a write <strong>ahead</strong> log.</li>
<li>Even more specifically, the backend process (the server&rsquo;s handler for the specific client session) first constructs what it wants to write into the log</li>
<li>After that, in PostgreSQL we only have a single WAL log, and only one backend can flush it at a time.<br>
The backend process has to request a lock on the WAL, preventing other processes from interacting with it at the same time.</li>
<li>After writing the already constructed data to the in-memory buffer, we have to write it to disk and then immediately flush the buffer to disk, to make sure that it is durable, since this is the log we are using for crash recovery.</li>
<li>It can release that lock only after that write/flush was done.</li>
<li>After releasing the lock, another backend can take it, repeating the process from (3)</li>
</ol>
<pre class="mermaid">
sequenceDiagram
participant B1 as Backend 1
participant B2 as Backend 2
participant L as Single WAL lock
B1-&gt;&gt;B1: construct WAL record
B2-&gt;&gt;B2: construct WAL record
B1-&gt;&gt;L: acquire
Note over B1,L: encrypt + write + flush<br>(everyone else waits)
B2--xL: blocked
L--&gt;&gt;B1: release
B2-&gt;&gt;L: acquire
Note over B2,L: encrypt + write + flush
L--&gt;&gt;B2: release
</pre>
<p>The above information is a bit oversimplified, as WAL writes are more complex than this, but it is already enough to spot the concurrency issue hidden in it:<br>
only one process can write/flush the WAL at a time, so no matter how many cores we have, it won&rsquo;t get faster.<br>
In fact it is the opposite, since higher core count CPUs usually have worse single-thread performance.</p>
<p>Most WAL encryption implementations, including pg_tde, implement it similarly to data file encryption:<br>
we encrypt the WAL data immediately before writing it to disk, at the time when the backend already acquired the WAL lock.<br>
Not only do we do additional computations for the current session, we also prevent other sessions from doing anything during this extra time.</p>
<p>In my test setup with the above numbers, WAL writes were already CPU bound without encryption.<br>
Even if encryption is relatively cheap, if we don&rsquo;t have spare cycles, it will show up.<br>
That&rsquo;s bad enough already with a good implementation, but if we also manage to introduce a performance-hurting bug in this area of the code, we can easily end up with quite bad numbers.</p>
<h3 id="is-this-completely-fixable">Is this completely fixable?<a class="anchor-link" id="is-this-completely-fixable"></a></h3>
<p>As I hinted at the beginning of the WAL section, this issue is fixable.<br>
WAL encryption doesn&rsquo;t inherently require encrypting more data than data file encryption does. In fact, if done correctly, most of the time we&rsquo;ll have to encrypt even less.</p>
<p>The problem is that we have to do it in a more challenging part of the code.</p>
<p>From the above description, it might already be clear:<br>
we should encrypt the data after we constructed it, before taking the WAL lock!</p>
<p><strong>Current: encrypt inside the lock</strong></p>
<pre class="mermaid">
flowchart LR
A[construct record] --&gt; B[acquire lock]
subgraph lock [lock held, serialized]
direction LR
C[encrypt] --&gt; D[write + flush]
end
B --&gt; C
D --&gt; E[release lock]
</pre>
<p><strong>Fixed: encrypt before the lock</strong></p>
<pre class="mermaid">
flowchart LR
A[construct record] --&gt; C[encrypt]
C --&gt; B[acquire lock]
subgraph lock [lock held, serialized and shorter]
direction LR
D[write + flush]
end
B --&gt; D
D --&gt; E[release lock]
</pre>
<p>That solves both the concurrency limitation and another problem hidden there:<br>
imagine that we are writing many small records, one per transaction.<br>
A single WAL record might be less than 100 bytes, but a WAL page is 8kB.</p>
<p>Even if we do it at flush time, we don&rsquo;t have to encrypt the entire page, only what we filled so far, but on average even that results in 4kB data per flush.<br>
With a 100-byte WAL record, we can fit around 80 records into a single WAL page.<br>
We have written 8kB of real data, and encrypted 320kB of WAL to do it.<br>
There are of course some possible optimizations there, for example we completely ignored group commit, which will likely make that 320kB number much smaller, but it still remains significantly more.</p>
<p>While moving the encryption before the lock might seem like an easier choice, it has different challenges.<br>
For example, there&rsquo;s one I already mentioned in the beginning: if we start encrypting small blocks (and also changing the stream configuration, which is also related to this), we get worse encryption performance.</p>
<p>The naive implementation of this approach performs even worse than our earlier pg_tde implementation.<br>
However, if we apply some optimizations to it, it can reach similar &ldquo;unmeasurable&rdquo; levels as the data page encryption.</p>
<p>This improvement is not yet included in pg_tde, as it requires a completely different approach to WAL encryption compared to our previous implementations, and we haven&rsquo;t yet finished testing and measuring it.<br>
Mainly, in this blog post I only focused on the performance of <em>writing</em> the WAL, but we have to remember that we also have to <em>read</em> it in some cases.<br>
While the speed of crash recovery isn&rsquo;t that important in a happy scenario, when something bad happens it does matter how quickly we can get our server running again.</p>
<h3 id="whats-the-test-scenario">What&rsquo;s the test scenario?<a class="anchor-link" id="whats-the-test-scenario"></a></h3>
<p>Similarly to the write bottlenecks above, a normal OLTP read-write workload won&rsquo;t showcase significant degradation because of WAL encryption.<br>
This is exactly why I wrote such a long post: the interesting behavior only shows up in scenarios a quick benchmark never exercises.</p>
<p>We could simply publish a nice graph showing the typical numbers, claiming that our encryption is fast.<br>
We could treat the performance problem we fixed as a small footnote in our changelog, as it doesn&rsquo;t show up at all in that measurement.</p>
<p>But that wouldn&rsquo;t be honest, because the test setup and test scenarios do matter, and when we talk about performance, we should mention worst-case numbers.</p>
<p>To reach these bad numbers, we have to do one thing:<br>
generate a huge amount of WAL across many sessions.<br>
This is doable in many different ways, ours is basically the following:</p>
<ol>
<li>create a few tables, each with a few million rows</li>
<li>start executing <code>UPDATE t SET c=c+1 WHERE id IN (SELECT id FROM t ORDER BY random() LIMIT 100);</code></li>
<li>run step 2 in many sessions</li>
</ol>
<p>The query above is a simplified illustration. The actual test used sequential IDs, with the 100 random IDs generated by the benchmark tool and inserted directly into a prepared statement, so selecting the rows stays cheap and the workload is dominated by the WAL writes.</p>
<p>PostgreSQL has an option called <a href="https://wiki.postgresql.org/wiki/Full_page_writes" target="_blank" rel="noopener noreferrer"><code>full_page_writes</code></a>, which is enabled by default.<br>
With it, when we modify any database page for the first time after a checkpoint, it doesn&rsquo;t only WAL log the modified row, instead it logs the entire page, 8kB.<br>
In the above query, for every update, we modify 100 randomly selected rows in a table.<br>
Because they are randomly selected from millions of rows, they have a good chance of being on different pages.<br>
Let&rsquo;s say that with a specific dataset, on average we hit a 10% new page rate &ndash; one update has to do 10 full page writes.<br>
That means every single UPDATE we execute generates more than 80kB of WAL.</p>
<p>This is again an oversimplification, but it is a good enough approximation without going into details too much.</p>
<p>With 15,000 QPS, that&rsquo;s around 1.2 GB/s of WAL.<br>
From the table at the beginning, on my test PC AES-128-CTR has around 8 GB/s single core bandwidth.<br>
1.2 GB/s is 15% of that &ndash; and that assumes no context switching or other losses, meaning no matter how good the integration into the database is, if we do encryption at this volume while holding a global lock, we have to expect at least 15% performance drop with these numbers.<br>
In practice it&rsquo;s a bit more than that.</p>
<h2 id="summary">Summary<a class="anchor-link" id="summary"></a></h2>
<p>I hope this explanation was more useful than a single graph showcasing how good our performance with pg_tde is.<br>
The worst-case scenarios I described here are corner cases, most servers don&rsquo;t generate sustained gigabytes per second of WAL, even in production.</p>
<p>But I still think this is important to mention, as it is an easily overlooked detail caused by the combination of PostgreSQL&rsquo;s WAL architecture and the simple way most vendors add encryption to it.</p>
<p>I also didn&rsquo;t go into detail on many parts of this post.<br>
If I tried to explain everything in absolute detail, this would be ten times longer, and much harder to follow and understand.<br>
I do plan to touch on some of these subjects in separate blog posts, where we can focus only on those topics in more depth.<br>
If you have questions or suggestions about the topic, don&rsquo;t hesitate to reach out using our <a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">community forums</a>!</p>

<p><a href="https://percona.community/blog/2026/07/20/tde-performance-in-postgresql/">TDE performance in PostgreSQL</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>ClickHouse Schema Design and Data Modeling</title>
      <link rel="alternate" type="text/html" href="https://severalnines.com/blog/clickhouse-schema-design-and-data-modeling/" />
      <id>https://severalnines.com/blog/clickhouse-schema-design-and-data-modeling/</id>
      <updated>2026-07-17T12:48:53+00:00</updated>
      <author><name>Agus Syafaat</name></author>
      <summary type="html"><![CDATA[<p>Sometimes, we see ClickHouse queries that should normally complete in milliseconds take several seconds to finish or worse, time out entirely. When that happens, there is a good chance that the schema is the real culprit. The problem is often not the query itself, nor is it a hardware bottleneck. Instead, it can stem from […]<br />
The post ClickHouse Schema Design and Data Modeling appeared first on Severalnines.</p>
<p><a href="https://severalnines.com/blog/clickhouse-schema-design-and-data-modeling/">ClickHouse Schema Design and Data Modeling</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Sometimes, we see ClickHouse queries that should normally complete in milliseconds take several seconds to finish or worse, time out entirely. When that happens, there is a good chance that the schema is the real culprit. The problem is often not the query itself, nor is it a hardware bottleneck. Instead, it can stem from a schema design decision made months ago that nobody questioned at the time.</p>
<p>Schema design in ClickHouse is one of those tasks that appears to be a simple, one-time setup activity, but it often becomes a recurring operational concern. When the schema is designed poorly, problems tend to surface over time, including slow queries, oversized partitions, and mutation jobs that run for hours while production traffic continues to grow.&nbsp;</p>
<p>In this article, we will cover the core concepts of ClickHouse, systematically walk through design decisions, from core concepts and operational patterns to monitoring and evolution, with the goal of giving you a framework for making and maintaining schema decisions in production.</p>
<h2 class="wp-block-heading" id="h-core-concepts-for-clickhouse-schema-design">Core Concepts for ClickHouse Schema Design<a class="anchor-link" id="core-concepts-for-clickhouse-schema-design"></a></h2>
<h3 class="wp-block-heading" id="h-distributed-tables-local-tables-shards-and-replicas">Distributed Tables, Local Tables, Shards, and Replicas<a class="anchor-link" id="distributed-tables-local-tables-shards-and-replicas"></a></h3>
<p>Before writing a single CREATE TABLE, it helps to have a clear mental model of how ClickHouse actually stores and serves data across a cluster. ClickHouse divides data across shards with each shard holding a horizontal slice of the total dataset. Each shard can have one or more replicas for fault tolerance. The replicas within a shard hold identical data; the shards themselves hold different data.</p>
<p>The two table types you&rsquo;ll work with constantly are:</p>
<ul class="wp-block-list">
<li>Local tables (<code>ReplicatedMergeTree</code> and its variants): the actual storage layer. Each node stores its own local table containing its shard&rsquo;s data. Queries against a local table only see that node&rsquo;s data.</li>
<li>Distributed tables (<code>Distributed</code> engine): a logical routing layer that sits on top of the local tables. When you query a distributed table, ClickHouse fans the query out to all shards, collects the results, and merges them. Distributed tables don&rsquo;t store data themselves.</li>
</ul>
<p><strong>N.B. schema changes need to be applied to local tables on every node, and the distributed table definition needs to match. It sounds obvious, but it is a common source of confusion when onboarding teams who are used to a single-server database.</strong></p>
<p>Shard key selection matters for data distribution. A poorly chosen shard key (or <code>rand()</code> used as a lazy default) can lead to uneven data distribution, e.g. one shard holding 60% of the data while others hold 20% each &mdash; this creates hot spots and makes capacity planning unreliable. The shard key should distribute data evenly and, ideally, align with how you query, if most queries filter by <code>tenant_id</code>, sharding by <code>tenant_id</code> means queries for a single tenant hit one shard instead of all of them.</p>
<h3 class="wp-block-heading" id="h-partition-key-and-primary-key-sparse-index">Partition Key and Primary Key (Sparse Index)<a class="anchor-link" id="partition-key-and-primary-key-sparse-index"></a></h3>
<p>These two concepts trip up almost everyone coming from a relational background, because they sound like the same thing but serve entirely different purposes in ClickHouse. <strong>The partition key</strong> controls how data is physically divided into separate directories on disk. Each partition is stored and managed independently, which means:</p>
<ul class="wp-block-list">
<li>Queries that filter on the partition key can skip entire partitions without reading them (partition pruning)</li>
<li>Old data can be dropped by dropping a partition, instant, no heavy delete operation</li>
<li>Background merges only happen within a partition, not across them</li>
</ul>
<p>For time-series data, partitioning by month (<code>toYYYYMM(event_time)</code>) is the most common pattern. It gives you clean data lifecycle management (drop old months instantly) and good pruning behavior for time-bounded queries.</p>
<p><strong>The primary key</strong> in ClickHouse is not a uniqueness constraint, it&rsquo;s a sparse index. ClickHouse stores one index entry per 8192 rows (one granule), not one per row. This makes it memory-efficient even at billions of rows, but it means the primary key is designed for range scans and filtering, not point lookups.</p>
<p>The <code>ORDER BY</code> clause defines the physical sort order of data on disk, and the primary key must be a prefix of <code>ORDER BY</code>. This is worth saying clearly: the sort order is what makes your queries fast or slow. If your most common query filters by <code>(tenant_id, event_type, event_time)</code>, your <code>ORDER BY</code> should reflect that. Data is stored sorted by those columns, so ClickHouse can skip irrelevant granules efficiently.</p>
<p>Here&rsquo;s a concrete example that puts these together:</p>
<pre class="wp-block-code"><code>CREATE TABLE events_local
(
    tenant_id     UInt32,
    event_time    DateTime,
    event_type    LowCardinality(String),
    user_id       UInt64,
    session_id    UUID,
    properties    String,
    ingested_at   DateTime DEFAULT now()
)
ENGINE = ReplicatedMergeTree(
    '/clickhouse/tables/{shard}/events',
    '{replica}'
)
PARTITION BY toYYYYMM(event_time)
ORDER BY (tenant_id, event_type, event_time)
SETTINGS index_granularity = 8192;</code></pre>
<p>A few decisions that can be taken as below:</p>
<ul class="wp-block-list">
<li><code>LowCardinality(String)</code> for <code>event_type</code>, if this column has fewer than 10,000 distinct values, this encoding dramatically reduces storage and speeds up filtering.</li>
<li><code>PARTITION BY toYYYYMM(event_time)</code>, monthly partitions, suitable for a 12&ndash;18 month hot data retention window.</li>
<li><code>ORDER BY (tenant_id, event_type, event_time)</code>, optimized for queries that filter by tenant first, then by event type, then narrow by time range.</li>
<li>The ZooKeeper path uses <code>{shard}</code> and <code>{replica}</code> macros so the same DDL can be run on every node without modification.</li>
</ul>
<h3 class="wp-block-heading">Materialized Views and Aggregated Tables<a class="anchor-link" id="materialized-views-and-aggregated-tables"></a></h3>
<p>Materialized views in ClickHouse are not the same as in PostgreSQL. They are real-time incremental aggregations; every time data is inserted into the source table, the materialized view processes those rows and writes the aggregated result to a target table. There&rsquo;s no scheduled refresh and it happens synchronously with the insert.</p>
<p>This makes them powerful for pre-computing aggregations that would otherwise require scanning billions of rows at query time. A common pattern is to maintain hourly or daily rollup tables alongside the raw events table.&nbsp;</p>
<p>For example, create the target table for aggregated counts and later create the <code>MATERIALIZED VIEW</code> with aggregation.</p>
<pre class="wp-block-code"><code>CREATE TABLE events_hourly_agg
(
    tenant_id    UInt32,
    event_type   LowCardinality(String),
    hour         DateTime,
    event_count  AggregateFunction(count, UInt64)
)
ENGINE = AggregatingMergeTree()
PARTITION BY toYYYYMM(hour)
ORDER BY (tenant_id, event_type, hour);



CREATE MATERIALIZED VIEW events_to_hourly
TO events_hourly_agg
AS
SELECT
    tenant_id,
    event_type,
    toStartOfHour(event_time) AS hour,
    countState() AS event_count
FROM events_local
GROUP BY tenant_id, event_type, hour;</code></pre>
<p>Operationally, materialized views add write amplification, every insert into the source table triggers a write to the view&rsquo;s target table. For high-ingestion workloads, this is worth monitoring. They also need to be maintained when the source schema changes, which is often forgotten until something breaks.</p>
<h2 class="wp-block-heading">Operational Design Patterns<a class="anchor-link" id="operational-design-patterns"></a></h2>
<h3 class="wp-block-heading">Time-Series and Event Analytics Schemas<a class="anchor-link" id="time-series-and-event-analytics-schemas"></a></h3>
<p>The vast majority of ClickHouse deployments are built around time-series or event data clickstreams, application logs, metrics, IoT sensor readings. This is where ClickHouse&rsquo;s design shines, and there are well-established patterns to follow.</p>
<p>The core principle is time as the primary organizing dimension. Partition by time (monthly or weekly depending on data volume), and include <code>event_time</code> in the <code>ORDER BY</code> so range scans are efficient. Keep raw events immutable and resist the temptation to update them in place.</p>
<p>For retention management, the TTL clause handles automatic expiry without manual intervention:</p>
<pre class="wp-block-code"><code>TTL event_time + INTERVAL 90 DAY DELETE

TTL event_time + INTERVAL 30 DAY TO DISK 'cold_storage'</code></pre>
<p>The first script automatically deletes rows older than 90 days while the second scripts move cold data to a cheaper storage tier. This is operationally cleaner than scheduled delete jobs, which in ClickHouse would trigger heavy mutations.</p>
<h2 class="wp-block-heading">Bulk Ingestion vs. Real-Time Streaming<a class="anchor-link" id="bulk-ingestion-vs-real-time-streaming"></a></h2>
<p>How data arrives significantly affects schema and operational behavior. ClickHouse handles both, but they stress the system differently.</p>
<p>Bulk ingestion, i.e. large batch inserts; for example, nightly ETL from a data warehouse, is relatively forgiving. ClickHouse is designed for large INSERT batches, each batch creates one or a few data parts, and the background merge process handles compaction.&nbsp;</p>
<p>The risk is inserting too many small batches in rapid succession, which creates a flood of tiny parts that overwhelm the merge queue. The rule of thumb is: batch size matters more than frequency. Aim for inserts of at least 10,000 &ndash;100,000 rows per batch.</p>
<p>Real-time streaming via Kafka requires more care. The ClickHouse Kafka table engine or tools like Vector/Benthos handle ingestion, but the operational concern is the same: small, frequent inserts create merge pressure. Configure consumers to buffer and batch messages before inserting, and monitor <code>system.parts</code> for signs of part accumulation.</p>
<pre class="wp-block-code"><code>SELECT
    table,
    count() AS part_count,
    sum(rows) AS total_rows,
    formatReadableSize(sum(bytes_on_disk)) AS disk_size
FROM system.parts
WHERE active = 1
GROUP BY table
ORDER BY part_count DESC;</code></pre>
<p>A healthy table has tens to low hundreds of active parts. Thousands of parts is a warning sign that inserts are too small or merges are falling behind.</p>
<h3 class="wp-block-heading">Multi-Tenant Schema Isolation<a class="anchor-link" id="multi-tenant-schema-isolation"></a></h3>
<p>If your ClickHouse cluster serves multiple tenants, you need to decide early how to isolate their data. The main options are:</p>
<ul class="wp-block-list">
<li>Database-per-tenant: each tenant gets their own database (and potentially their own set of tables). Clean isolation, simple access control, but doesn&rsquo;t scale past a few dozen tenants without becoming a management burden.</li>
<li>Table-per-tenant: all tenants share a database, each with their own table. Works at moderate scale but schema changes need to be applied to every tenant table, which is operationally painful at hundreds of tenants.</li>
<li>Shared table with <code>tenant_id</code> column: all tenant data in one table, filtered by <code>tenant_id</code>. This is the most operationally maintainable pattern at scale. The key requirement is that <code>tenant_id</code> must be the leading column in <code>ORDER BY</code> so that per-tenant queries efficiently skip irrelevant data without a full scan.</li>
</ul>
<pre class="wp-block-code"><code>ORDER BY (tenant_id, event_type, event_time)</code></pre>
<p>With this sort order, a query filtering on <code>tenant_id = 42</code> skips all granules that don&rsquo;t contain that tenant&rsquo;s data, making it effectively as fast as if the table contained only that tenant&rsquo;s rows.</p>
<h2 class="wp-block-heading">Schema Evolution and Operational Impact<a class="anchor-link" id="schema-evolution-and-operational-impact"></a></h2>
<h3 class="wp-block-heading">Adding Columns, Partitions, and Handling Mutations<a class="anchor-link" id="adding-columns-partitions-and-handling-mutations"></a></h3>
<p>Schema changes in ClickHouse are generally safer than in OLTP databases, but they are not without operational cost. Adding a column is fast and non-blocking. ClickHouse uses lazy evaluation, the new column returns a default value for existing rows without rewriting data on disk. It is one of the rare DDL operations you can run in production without much anxiety:</p>
<pre class="wp-block-code"><code>ALTER TABLE events_local ON CLUSTER my_cluster
ADD COLUMN geo_country LowCardinality(String) DEFAULT '';</code></pre>
<p>Dropping a column triggers a background data rewrite (mutation) to remove that column from existing parts. This is heavier and can be slow on large tables. Mutations are expensive in ClickHouse. For example when you run the following command, ClickHouse does not update the row in place but finds all parts with the matching condition, creates new versions of those parts with the modification already applied, replaces old parts after processing and continues serving queries while mutations run in the background.</p>
<pre class="wp-block-code"><code>ALTER TABLE events_local
UPDATE status = 'processed'
WHERE id = 123; </code></pre>
<p>The guidance here is simple: avoid mutations in hot paths. For data corrections, prefer inserting corrected rows and using a <code>ReplacingMergeTree</code> or <code>CollapsingMergeTree</code> engine to handle deduplication, rather than updating rows in place.</p>
<p>If you must run a mutation, monitor its progress:</p>
<pre class="wp-block-code"><code>SELECT
    command,
    parts_to_do,
    is_done,
    latest_fail_reason
FROM system.mutations
WHERE table = 'events_local' AND is_done = 0;</code></pre>
<h2 class="wp-block-heading">Monitoring Schema-Related Issues<a class="anchor-link" id="monitoring-schema-related-issues"></a></h2>
<h3 class="wp-block-heading">Identifying Slow Queries and Partition Problems<a class="anchor-link" id="identifying-slow-queries-and-partition-problems"></a></h3>
<p>The most useful table in ClickHouse for day-to-day schema health monitoring is <code>system.query_log</code>. Queries that are reading an unexpectedly high number of rows relative to what they return are usually a sign of poor partition pruning or an <code>ORDER BY</code> that doesn&rsquo;t align with the filter. <strong>Skipping indexes</strong> (secondary indexes in ClickHouse) are often added with good intentions but not actually used. Check whether they&rsquo;re being utilized by execute the following:</p>
<pre class="wp-block-code"><code>SELECT
    table,
    name,
    type,
    expr
FROM system.data_skipping_indices
WHERE database = 'mydb';</code></pre>
<p>Then cross-reference with <code>system.query_log</code> to see if queries against that table are actually benefiting, if <code>read_rows</code> remains high after adding an index, it may not be matching the query pattern.</p>
<h3 class="wp-block-heading">Capacity Planning for Growth<a class="anchor-link" id="capacity-planning-for-growth"></a></h3>
<p>Schema decisions have long-term storage implications that are not always obvious at design time. A few metrics are worth tracking regularly, such those included in this storage growth per table over time monitoring query below:</p>
<pre class="wp-block-code"><code>SELECT
    table,
    formatReadableSize(sum(bytes_on_disk)) AS total_size,
    sum(rows) AS total_rows,
    count() AS part_count,
    max(modification_time) AS last_modified
FROM system.parts
WHERE active = 1 AND database = 'mydb'
GROUP BY table
ORDER BY sum(bytes_on_disk) DESC;</code></pre>
<p>Track the table with total size, rows, partition count on a weekly basis and plot the trend. A table that grows 20% month-over-month with a 90 day TTL will eventually reach a stable size but a table with no TTL and unbounded growth will eventually cause disk pressure that affects the entire cluster.</p>
<p>Partition-level granularity is also useful for anticipating when TTL drops will occur and what storage they will free:</p>
<pre class="wp-block-code"><code>SELECT
    partition,
    formatReadableSize(sum(bytes_on_disk)) AS size,
    sum(rows) AS rows,
    count() AS parts
FROM system.parts
WHERE active = 1 AND table = 'events_local'
GROUP BY partition
ORDER BY partition DESC;</code></pre>
<p>The above query shows the size per partition for the ClickHouse table <code>events_local</code>.&nbsp;</p>
<h2 class="wp-block-heading">Integrating with Your Multi-Database Environment<a class="anchor-link" id="integrating-with-your-multi-database-environment"></a></h2>
<h3 class="wp-block-heading">Data Flow from OLTP to ClickHouse<a class="anchor-link" id="data-flow-from-oltp-to-clickhouse"></a></h3>
<p>Most ClickHouse deployments exist downstream of an OLTP database. Orders come in through PostgreSQL, user events flow through MySQL, and ClickHouse ingests and aggregates that data for analytics. This pipeline introduces a class of schema problems that don&rsquo;t exist in single-database setups.</p>
<p>The OLTP schema and the ClickHouse schema should not be the same schema. OLTP tables are normalized, they are designed to minimize write amplification and enforce referential integrity. ClickHouse schemas are deliberately denormalized, trading write efficiency for read efficiency. A join that&rsquo;s trivial in PostgreSQL can be expensive in ClickHouse at scale, so the right pattern is to resolve joins at ingestion time, pushing denormalized, enriched records into ClickHouse rather than replicating normalized tables and joining at query time.</p>
<p>This means the ingestion pipeline, whether it&rsquo;s Kafka, Debezium CDC, Airbyte, or a custom ETL, is also a transformation layer. Fields get renamed, types get cast, related records get joined and flattened, and low-cardinality string fields get encoded appropriately. Operationally, this pipeline is part of the schema: changes to it have the same impact as changes to the table definition.</p>
<h3 class="wp-block-heading">Managing Model Changes, Versioning, and Rollback<a class="anchor-link" id="managing-model-changes-versioning-and-rollback"></a></h3>
<p>When the upstream OLTP schema changes eg: a new column added to <code>orders</code>, a field renamed in <code>users</code>. The downstream ClickHouse schema and the ingestion pipeline both need to change in a coordinated way. Without a versioning discipline, these changes become brittle and difficult to roll back &mdash; a few practices that hold up well in production:</p>
<ul class="wp-block-list">
<li>Treat DDL as code: The schema changes should live in version-controlled migration files (tools like Flyway or a custom migration runner), not applied ad-hoc from a SQL client. Every <code>ALTER TABLE</code> that went to production should be traceable to a commit.</li>
<li>Add before you remove: When renaming a column or changing a type, add the new column first and allow both the old and new column to coexist during a transition window. Update the ingestion pipeline to write to both, then cut over queries to the new column, then drop the old one. This avoids a hard cutover that can&rsquo;t be rolled back.</li>
<li>Schema rollback is hard therefore plan for it: Dropping a column or partition key change is not easily reversible. Before applying significant schema changes, take a backup of the affected table (or at minimum its most recent partition) so that recovery is possible without a full cluster restore.</li>
<li>Document the lineage: For each ClickHouse table, maintain a short document describing where the data comes from, what transformations are applied, and what downstream queries or dashboards depend on it. When a schema change is proposed, this lineage makes the blast radius obvious before anything is applied.</li>
</ul>
<h2 class="wp-block-heading">Conclusion<a class="anchor-link" id="conclusion"></a></h2>
<p>ClickHouse schema design is not something that you set once, but is something you evolve over time. The implication is that choices you make when creating a table today do not just affect today&rsquo;s queries but quietly shape how your system performs months down the line, from how efficiently queries run to how painful or painless future schema changes turn out to be.</p>
<p>Some points worth keeping in mind when designing the schema and data modeling: <strong>design</strong> your <code>ORDER BY</code> for readers, not writers; <strong>structure</strong> your sort key around how people query the data, not around the order it arrives in; <strong>partition</strong> by time and but avoid slicing things so finely that merge overhead becomes its own problem; <strong>Be deliberate</strong> with data types. <code>LowCardinality</code> and <code>AggregateFunction</code> are powerful tools, but only when applied with clear intent. Reaching for them out of habit rather than purpose tends to backfire. Your <strong>ingestion pipeline</strong> is part of your schema &mdash; how data flows in isn&rsquo;t separate from how it&rsquo;s stored, think of them as one connected system.&nbsp;</p>
<p>And remember, keep an eye on the correct metrics from the start. Schema issues seldom make themselves known in an obvious way. Identifying them through regular monitoring is much less expensive than dealing with the aftermath. The central idea here is that decisions regarding schemas have a cumulative effect. Positive choices subtly simplify all other aspects, while negative ones discreetly complicate them.</p>
<p>The post <a href="https://severalnines.com/blog/clickhouse-schema-design-and-data-modeling/">ClickHouse Schema Design and Data Modeling</a> appeared first on <a href="https://severalnines.com">Severalnines</a>.</p>

<p><a href="https://severalnines.com/blog/clickhouse-schema-design-and-data-modeling/">ClickHouse Schema Design and Data Modeling</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Perconians at WeAreDevelopers World Congress 2026: Agents Everywhere, Security Wake-Up Calls, and Buzzword Bingo</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/07/17/wearedevelopers-2026/" />
      <id>https://percona.community/blog/2026/07/17/wearedevelopers-2026/</id>
      <updated>2026-07-17T11:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>On July 9–10, the two of us - Sandra (Engineering, Percona for MongoDB) and Radek (Product, Percona for MongoDB) - packed our backpacks and headed to Berlin for the WeAreDevelopers World Congress Europe 2026 (WAD). The 11th edition of the congress gathered 15,000 developers and 500+ speakers for two intense days, and we came back with full notebooks, fresh ideas, and one very clear message from the industry.</p>
<p><a href="https://percona.community/blog/2026/07/17/wearedevelopers-2026/">Perconians at WeAreDevelopers World Congress 2026: Agents Everywhere, Security Wake-Up Calls, and Buzzword Bingo</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>On July 9&ndash;10, the two of us &ndash; <strong>Sandra</strong> (Engineering, Percona for MongoDB) and <strong>Radek</strong> (Product, Percona for MongoDB) &ndash; packed our backpacks and headed to Berlin for the <a href="https://www.wearedevelopers.com/world-congress" target="_blank" rel="noopener noreferrer">WeAreDevelopers World Congress Europe 2026</a> (WAD). The 11th edition of the congress gathered <strong>15,000 developers and 500+ speakers</strong> for two intense days, and we came back with full notebooks, fresh ideas, and one very clear message from the industry.</p>

<p>
At Percona, we genuinely love getting out of our daily routine to learn what&rsquo;s new. It is incredibly refreshing to escape the daily grind of sprints and stand-ups just to listen and absorb. Hearing how other teams are tackling scale and complexity reminds us that we are all solving different flavors of the same core problems. These events act as a catalyst for innovation, sparking conversations that inevitably push our own boundaries. Ultimately, taking a couple of days to zoom out and see where the industry is heading helps us build what Sandra calls a <em>mental index</em> &ndash; concepts that might not solve today&rsquo;s ticket, but will absolutely pay off six months from now.</p>
<p>While we took notes across a huge variety of topics, one common thread stood out above the rest. This post is our attempt to share that index with you, with links so you can dig deeper into whatever catches your eye.</p>
<p>Spoiler: if you played buzzword bingo with &ldquo;agentic AI,&rdquo; you&rsquo;d have won in the first hour.</p>
<h2 id="the-one-big-theme-agentic-ai-surprise">The one big theme: Agentic AI (surprise!)<a class="anchor-link" id="the-one-big-theme-agentic-ai-surprise"></a></h2>
<p>Every conference right now has an AI theme, but WAD went deep: adoption stories, best practices, building and optimizing RAG pipelines, and &ndash; importantly &ndash; what happens to security when agents write and ship code.</p>
<p>Our personal top takeaways:</p>
<ul>
<li><strong>Security matters more than ever in the age of AI.</strong> More on that below &ndash; this one deserves its own section.</li>
<li><strong>Agents are only as good as their context.</strong> Intent, mission, purpose, style, goals, success metrics &ndash; the teams winning with AI are the ones writing this down for their agents.</li>
<li><strong>Understand <em>why</em> you do what you do.</strong> Agents won&rsquo;t think about that for us. The engineering judgment moves up the stack; it doesn&rsquo;t disappear.</li>
</ul>
<h2 id="the-sdlc-is-dead---the-agentic-assembly-line-keynote">&ldquo;The SDLC is dead&rdquo; &ndash; the Agentic Assembly Line keynote<a class="anchor-link" id="the-sdlc-is-dead-the-agentic-assembly-line-keynote"></a></h2>
<p>Thomas Dohmke (CEO of Entire, previously CEO of GitHub) opened with a demo-filled keynote about where developers and agents are headed. A few numbers that made the whole room sit up:</p>
<ul>
<li>Teams using coding agents ship <strong>5&times; more code</strong> (measured by PRs), and PRs are <strong>3&times; bigger</strong> than 18 months ago.</li>
<li><strong>20%+ of AI-generated changes are accepted without human review.</strong> (Brave? Terrifying? Discuss.)</li>
<li>Some products &ndash; Codex, notably &ndash; are now written <em>only</em> by AI.</li>
</ul>
<p>His conclusion: <strong>the Software Development Lifecycle as we know it is dead.</strong> The DevOps loop is evolving into what he called <a href="https://medium.com/@tentenco/what-is-ralph-loop-a-new-era-of-autonomous-coding-96a4bb3e2ac8" target="_blank" rel="noopener noreferrer">&ldquo;The Ralph Loop&rdquo;</a> &ndash; a much faster path from code to production, with developers acting as verifiers, because agents still fail often. The winning team, in his view, is the one whose agents understand the company&rsquo;s mission, values, and purpose.</p>
<p>One practical problem he highlighted: when you code with agents, context fragments across chats, prompts, sessions, and branches. Tools like <a href="https://entire.io/" target="_blank" rel="noopener noreferrer">entire.io</a> now store the chat sessions behind each PR right in the GitHub repository &ndash; so the <em>intent</em> behind a change doesn&rsquo;t evaporate. You may want to check this tool out! Let&rsquo;s see how the GitHub, we know today, evolves over the next decade.</p>
<h2 id="retrieval-is-the-weakest-link-in-your-rag">Retrieval is the weakest link in your RAG<a class="anchor-link" id="retrieval-is-the-weakest-link-in-your-rag"></a></h2>
<p>One of our favorite technical talks, by Tomek Porozynski (deepsense.ai), tackled the &ldquo;R&rdquo; in RAG (Retrieval Augmented Generation). General embedding models are trained on public data &ndash; they know general language, <strong>not your business</strong>.</p>
<p>Basic retrieval falls short: keyword search misses semantic context, and vector search misses exact terms, multi-step logic, document-wide context, and internal jargon. There&rsquo;s no single fix &ndash; you pick the right tool for the job.</p>

<p>For domain-specific knowledge, the fix is to fine-tune the embedding model on your own domain, so the vector space itself shifts to reflect your terminology and the real relationships between your terms.</p>
<p>The mechanics are surprisingly approachable: reshape the vector space through relative distances (pull matching pairs closer, push mismatched pairs apart), using either <strong>triplet loss</strong> or <strong>MultipleNegativesRankingLoss</strong> &ndash; and with the <a href="https://sbert.net/" target="_blank" rel="noopener noreferrer">Sentence Transformers</a> toolkit, the latter is literally one import away.</p>
<p>If you want to try it yourself, the speaker shared <a href="https://github.com/ontaptom/workshops/tree/main/notebooks" target="_blank" rel="noopener noreferrer">hands-on Colab notebooks</a>.</p>
<h2 id="the-security-wake-up-call-surviving-the-vulnpocalypse">The security wake-up call: surviving the &ldquo;Vulnpocalypse&rdquo;<a class="anchor-link" id="the-security-wake-up-call-surviving-the-vulnpocalypse"></a></h2>
<p>Adrian Mouat (Chainguard) delivered the talk that stuck with us the most. Advanced AI models can now autonomously discover and weaponize zero-day vulnerabilities at machine speed &ndash; effectively <strong>erasing the traditional patch window</strong>. Especially with the rise of <a href="https://www.anthropic.com/claude/mythos" target="_blank" rel="noopener noreferrer">Anthrophic Mythos</a> model, this might lead to Vulnpocalypse!</p>
<p>This is very serious for open source: attackers can point LLMs at public codebases, while underfunded maintainers face an overwhelming volume of newly discovered bugs. As people who live and breathe open source databases, this hits close to home.</p>
<p>The defenses he proposed:</p>
<ul>
<li><strong>Fight AI with AI</strong> &ndash; proactively scan your own infrastructure and find vulnerabilities before attackers do.</li>
<li><strong>Minimize your attack surface</strong> &ndash; fewer dependencies, and consider AI-written snippets over pulling in vulnerable third-party libraries.</li>
<li><strong>Strict hygiene</strong> &ndash; immediate patching and eliminating long-lived access tokens are non-negotiable.</li>
<li><strong>Industry coalitions</strong> &ndash; rapid-response groups like <a href="https://www.chainguard.dev/athena" target="_blank" rel="noopener noreferrer">Athena</a> share mitigations at machine speed, while &ldquo;Akrites&rdquo; safely funnels fixes back into upstream open source projects.</li>
</ul>
<p>At Percona, we&rsquo;re already evaluating joining these coalitions &ndash; stay tuned!</p>
<p>Related: Isha Salania (Microsoft) showed how <strong>confidential computing</strong> extends encryption to data <em>in use</em> &ndash; your prompts, retrieved chunks, and keys living in encrypted memory. For anyone building sovereign RAG systems on top of databases, this end-to-end view of data protection is worth understanding &ndash; and it&rsquo;s going to raise expectations for queryable encryption across the whole database ecosystem.</p>
<h2 id="mcp-doesnt-suck---your-agent-does">&ldquo;MCP doesn&rsquo;t suck &ndash; your agent does&rdquo;<a class="anchor-link" id="mcp-doesnt-suck-your-agent-does"></a></h2>
<p>Best talk title of the conference, courtesy of Jan Curn (Apify). The problem: most agents load <em>all</em> available tool schemas into the context window upfront, causing context rot, slow performance, and rapidly burning tokens. Their answer is <strong>mcpc</strong> &ndash; a lightweight CLI that enables <em>progressive tool discovery</em>: the agent fetches only the tool schemas it needs, on demand, and chains workflows through native code execution. Add OAuth 2.1 and sandboxed proxy connections, and you get a much saner MCP setup.</p>
<h2 id="more-gems-worth-your-time">More gems worth your time<a class="anchor-link" id="more-gems-worth-your-time"></a></h2>
<ul>
<li><strong>From SDLC to ADLC.</strong> Marcin Wawryszczuk (Andersen) argued that AI speeds up <em>coding</em> but not the <em>release cycle</em> &ndash; the industry needs an Agentic Delivery Lifecycle where agents help with requirements, architecture, docs, and pipelines, while engineers keep authority over architecture, governance, and validation.</li>
<li><strong>Don&rsquo;t lock in your AI tooling too early.</strong> Angie Jones shared how Block bought access to many tools and let engineers run with them &ndash; what works for a web developer doesn&rsquo;t work for a mobile or JVM developer, and that diversity of feedback is gold. Standardize when you see workflows succeeding repeatedly, not because a vendor made a good pitch.</li>
<li><strong>LLMs in the wild.</strong> GetYourGuide&rsquo;s data scientist Giampaolo Casolla and MLOps engineer Steven Mi walked through keeping an AI-driven recommendation system alive in production. Real numbers, real trade-offs.</li>
<li><strong>Platform-as-a-Product.</strong> Dominik Schmidle (Giant Swarm) on why internal platforms fail: happy users won&rsquo;t save your platform if the C-level sees it as pure cost. Know your user <em>and</em> your decision-maker &ndash; and do internal marketing.</li>
<li><strong>Werner Vogels (CTO, Amazon) fireside chat.</strong> Invisible work is important and worth sharing. Stay curious, never stop learning &ndash; and he recommended the book <a href="https://www.amazon.de/Ask-Your-Developer-Software-Developers/dp/0063018292" target="_blank" rel="noopener noreferrer"><em>Ask Your Developer</em></a> by Jeff Lawson.</li>
</ul>
<h2 id="the-expo-floor">The expo floor<a class="anchor-link" id="the-expo-floor"></a></h2>
<p>Between the talks, we&rsquo;ve also hung out at the Percona booth &ndash; yes, we&rsquo;ve been there the entire two days and chatting with 100+ visitors about what we love the most &ndash; databases!</p>

<p>We&rsquo;ve also visited our neighbours at the expo hall and had great conversations with them, too &ndash; but there was one that stood out:</p>
<p><a href="https://www.qodo.ai/" target="_blank" rel="noopener noreferrer"><strong>Qodo</strong></a> (formerly CodiumAI): AI code review that indexes your entire repository, so reviews understand the architectural &ldquo;why&rdquo; behind a change &ndash; and it&rsquo;s <a href="https://github.com/marketplace/qodo-merge-pro-for-open-source" target="_blank" rel="noopener noreferrer">free for open source projects</a>. We&rsquo;re excited to try it on our own projects.</p>
<h2 id="parting-words">Parting words<a class="anchor-link" id="parting-words"></a></h2>
<p>Two quotes from the WeAreDevelopers founders stayed with us. From CPO Thomas Pamminger:</p>
<blockquote>
<p>AI didn&rsquo;t make me worse at my job &ndash; it made it easier to be worse without noticing. Charles Eames, asked what he&rsquo;d delegate, said: <strong>never the understanding.</strong></p>
</blockquote>
<p>And from CEO Sead Ahmetovi&#263;:</p>
<blockquote>
<p>Someone, somewhere, will depend on what you ship next. That&rsquo;s not a burden &ndash; that&rsquo;s the whole point, because your work matters. Let&rsquo;s do it well.</p>
</blockquote>
<p>That&rsquo;s a pretty good summary of why we go to these events: to keep understanding, not just shipping.</p>
<p>If any of the topics above sparked something &ndash; RAG fine-tuning, supply chain security, vector search in databases &ndash; chat with us on the <a href="https://forums.percona.com/" target="_blank" rel="noopener noreferrer">Percona Community Forum</a> or just drop a comment here. We&rsquo;d love to hear what <em>you</em> took away from WAD if you were there.</p>
<p>See you at the next event!</p>

<p><a href="https://percona.community/blog/2026/07/17/wearedevelopers-2026/">Perconians at WeAreDevelopers World Congress 2026: Agents Everywhere, Security Wake-Up Calls, and Buzzword Bingo</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Introducing Mountaineers: A Way to Say Thank You</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/07/16/introducing-mountaineers/" />
      <id>https://percona.community/blog/2026/07/16/introducing-mountaineers/</id>
      <updated>2026-07-16T11:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>You filed a bug report at 11pm because you’d already done the work to isolate it. You answered a forum question that had been sitting unanswered for three days. You wrote a PR. You spent an hour on a call telling us what’s broken about a tool you use every day. None of that is small, and none of it should go unnoticed.</p>
<p><a href="https://percona.community/blog/2026/07/16/introducing-mountaineers/">Introducing Mountaineers: A Way to Say Thank You</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>You filed a bug report at 11pm because you&rsquo;d already done the work to isolate it. You answered a forum question that had been sitting unanswered for three days. You wrote a PR. You spent an hour on a call telling us what&rsquo;s broken about a tool you use every day. None of that is small, and none of it should go unnoticed.</p>
<p>That&rsquo;s what Mountaineers are. It&rsquo;s how we recognize the time and energy you put into this community &mdash; and reward it.</p>
<h2 id="why-we-built-this">Why we built this<a class="anchor-link" id="why-we-built-this"></a></h2>
<p>Every contribution to this community costs you something: time, expertise, patience. Writing reproduction steps for a bug isn&rsquo;t free. Neither is answering the same kind of question for the fifth newcomer this month, or sitting down with our engineering team to walk through how you actually use Percona Operators in production.</p>
<p>We see that. Mountaineers are our way of tracking it properly and giving something back &mdash; recognition, access, and yes, swag.</p>
<h2 id="what-counts">What counts<a class="anchor-link" id="what-counts"></a></h2>
<p>This isn&rsquo;t just about code. If you&rsquo;ve assumed that contributing means opening a pull request or nothing, that&rsquo;s not how this works. Points come from:</p>
<ul>
<li><strong>GitHub</strong> &mdash; issues, PRs, and merged contributions</li>
<li><strong>Forum</strong> &mdash; starting discussions, replying, and accepted solutions</li>
<li><strong>Content</strong> &mdash; blog posts, tutorials, and video appearances, including through our <a href="https://percona.community/blog/2026/05/22/write-for-percona-community/" target="_blank" rel="noopener noreferrer">Community Writers Program</a>, where you also get paid for published posts</li>
<li><strong>Direct feedback</strong> &mdash; 1:1 sessions with our engineering team and survey responses</li>
</ul>
<p>That last one matters more than people think. If you want to tell us what works, what doesn&rsquo;t, and how you&rsquo;re actually using our tools day to day, we want that conversation. Talk to engineering directly, or write it up for the blog. Either way, it counts.</p>
<h2 id="how-the-climb-works">How the climb works<a class="anchor-link" id="how-the-climb-works"></a></h2>
<p>Everyone starts at Basecamp. From there, the more you contribute &mdash; and the more places you contribute &mdash; the higher you climb. Show up across GitHub, the forum, and content in the same month, and your points multiply. We&rsquo;re not trying to make this complicated: more engagement, more recognition, faster.</p>
<p>Points convert into real rewards. Stickers and digital badges at the entry tier. T-shirts and water bottles as you climb. Hoodies and tech accessories further up. All those who begin the climb will receive a serialized Challenge Coin &mdash; the kind of thing you can&rsquo;t buy, only earn.</p>
<p>The people who consistently show up across the board get invited to take part in Percona Live: roadmap sessions, early access, time with the people building the tools you use.</p>
<h2 id="you-dont-need-a-long-resume-to-start">You don&rsquo;t need a long resume to start<a class="anchor-link" id="you-dont-need-a-long-resume-to-start"></a></h2>
<p>If you&rsquo;ve filed one bug report with clear reproduction steps, answered one forum question, or have an opinion about a tool you use that you&rsquo;ve never told us &mdash; you already have something to bring. We built Mountaineers to recognize the full range of ways people show up, not just the most visible ones.</p>
<p><strong><a href="https://forums.percona.com/signup" target="_blank" rel="noopener noreferrer">Sign up for Mountaineers</a></strong> and your GitHub and forum activity start counting from day one.</p>
<p>Want the full detail on points, rungs, and rewards? Read the <a href="https://percona.community/ascent/mountaineers/" target="_blank" rel="noopener noreferrer">Mountaineers program page</a>.</p>

<p><a href="https://percona.community/blog/2026/07/16/introducing-mountaineers/">Introducing Mountaineers: A Way to Say Thank You</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Multi-Cluster Replication, FIPS Mode, and More with MariaDB Enterprise Kubernetes Operator 26.06</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/multi-cluster-replication-fips-mode-and-more-with-mariadb-enterprise-kubernetes-operator-26-06/" />
      <id>https://mariadb.com/resources/blog/multi-cluster-replication-fips-mode-and-more-with-mariadb-enterprise-kubernetes-operator-26-06/</id>
      <updated>2026-07-15T17:13:46+00:00</updated>
      <author><name>Egor Ustinov</name></author>
      <summary type="html"><![CDATA[<p>What is the MariaDB Enterprise Kubernetes Operator? The MariaDB Enterprise Kubernetes Operator makes it easier to run and manage MariaDB […]</p>
<p><a href="https://mariadb.com/resources/blog/multi-cluster-replication-fips-mode-and-more-with-mariadb-enterprise-kubernetes-operator-26-06/">Multi-Cluster Replication, FIPS Mode, and More with MariaDB Enterprise Kubernetes Operator 26.06</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>The MariaDB Enterprise Kubernetes Operator makes it easier to run and manage MariaDB databases in Kubernetes. It automates day-to-day work such as deployment, scaling, backups, recovery, security configuration, and upgrades, reducing the manual effort of running MariaDB databases on Kubernetes. For more information, see the MariaDB Enterprise Kubernetes Operator page.</p>
<p><a href="https://mariadb.com/resources/blog/multi-cluster-replication-fips-mode-and-more-with-mariadb-enterprise-kubernetes-operator-26-06/" rel="nofollow">Source</a></p>

<p><a href="https://mariadb.com/resources/blog/multi-cluster-replication-fips-mode-and-more-with-mariadb-enterprise-kubernetes-operator-26-06/">Multi-Cluster Replication, FIPS Mode, and More with MariaDB Enterprise Kubernetes Operator 26.06</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Database Index Optimizer</title>
      <link rel="alternate" type="text/html" href="https://www.fromdual.com/blog/database_index_optimizer/" />
      <id>https://www.fromdual.com/blog/database_index_optimizer/</id>
      <updated>2026-07-15T14:55:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>Recently, a client asked me if the “time-consuming” task of checking indexes could be left to an index optimizer. Of course it can…<br />
What exactly do we want to check?</p>
<p>Tables without a Primary Key<br />
Duplicate indexes<br />
Partially redundant indexes<br />
Unused indexes</p>
<p>MariaDB, MySQL, and Percona Server<br />
Tables without a Primary Key<br />
SQL > SELECT DISTINCT t.table_schema, t.table_name<br />
 FROM information_schema.tables AS t<br />
 LEFT JOIN information_schema.columns AS c ON t.table_schema = c.table_schema AND t.table_name = c.table_name<br />
 AND c.column_key = \"PRI\"<br />
 WHERE t.table_schema NOT IN (\'information_schema\', \'mysql\', \'performance_schema\')<br />
 AND c.table_name IS NULL AND t.table_type NOT IN(\'VIEW\', \'SEQUENCE\')<br />
 AND t.table_schema = \'testtest\'<br />
;<br />
+--------------+------------+<br />
&#124; table_schema &#124; table_name &#124;<br />
+--------------+------------+<br />
&#124; testtest &#124; archived &#124;<br />
+--------------+------------+<br />
1 row in set<br />
Source: Tables without a Primary Key<br />
Duplicate indexes<br />
SQL > SELECT table_name, redundant_index_name, redundant_index_columns, dominant_index_name, dominant_index_columns, sql_drop_index<br />
 FROM sys.schema_redundant_indexes<br />
 WHERE redundant_index_columns = dominant_index_columns<br />
 AND table_schema = \'testtest\'<br />
;<br />
+------------+----------------------+-------------------------+---------------------+------------------------+------------------------------------------------------+<br />
&#124; table_name &#124; redundant_index_name &#124; redundant_index_columns &#124; dominant_index_name &#124; dominant_index_columns &#124; sql_drop_index &#124;<br />
+------------+----------------------+-------------------------+---------------------+------------------------+------------------------------------------------------+<br />
&#124; archived &#124; dupl2 &#124; category_id &#124; dupl1 &#124; category_id &#124; ALTER TABLE `testtest`.`archived` DROP INDEX `dupl2` &#124;<br />
+------------+----------------------+-------------------------+---------------------+------------------------+------------------------------------------------------+<br />
1 row in set<br />
Source: Duplicate and redundant indices<br />
Partially redundant indexes<br />
SQL > SELECT table_name, redundant_index_name, redundant_index_columns, dominant_index_name, dominant_index_columns, sql_drop_index<br />
 FROM sys.schema_redundant_indexes<br />
 WHERE table_schema = \'testtest\'<br />
;<br />
+-------------------+----------------------+-------------------------+---------------------+---------------------------------+-------------------------------------------------------------------+<br />
&#124; table_name &#124; redundant_index_name &#124; redundant_index_columns &#124; dominant_index_name &#124; dominant_index_columns &#124; sql_drop_index &#124;<br />
+-------------------+----------------------+-------------------------+---------------------+---------------------------------+-------------------------------------------------------------------+<br />
&#124; access &#124; customer &#124; customer &#124; customer_2 &#124; customer,callerid_internal &#124; ALTER TABLE `testtest`.`access` DROP INDEX `customer` &#124;<br />
&#124; access &#124; customer &#124; customer &#124; customer_3 &#124; customer,callerid_external &#124; ALTER TABLE `testtest`.`access` DROP INDEX `customer` &#124;<br />
&#124; active_customers &#124; uniqueid &#124; uniqueid &#124; PRIMARY &#124; uniqueid,scustomer &#124; ALTER TABLE `testtest`.`active_customers` DROP INDEX `uniqueid` &#124;<br />
&#124; analytics_include &#124; analytics &#124; analytics &#124; PRIMARY &#124; analytics,feature,dtype,dnumber &#124; ALTER TABLE `testtest`.`analytics_include` DROP INDEX `analytics` &#124;<br />
&#124; archived &#124; dupl2 &#124; category_id &#124; dupl1 &#124; category_id &#124; ALTER TABLE `testtest`.`archived` DROP INDEX `dupl2` &#124;<br />
...<br />
&#124; texts_media &#124; uniqueid &#124; uniqueid &#124; PRIMARY &#124; uniqueid,filename &#124; ALTER TABLE `testtest`.`texts_media` DROP INDEX `uniqueid` &#124;<br />
&#124; unlimited_access &#124; customer &#124; customer &#124; customer_2 &#124; customer,callerid_internal &#124; ALTER TABLE `testtest`.`unlimited_access` DROP INDEX `customer` &#124;<br />
&#124; unlimited_access &#124; customer &#124; customer &#124; customer_3 &#124; customer,callerid_external &#124; ALTER TABLE `testtest`.`unlimited_access` DROP INDEX `customer` &#124;<br />
+-------------------+----------------------+-------------------------+---------------------+---------------------------------+-------------------------------------------------------------------+<br />
26 rows in set<br />
Source: Duplicate and redundant indices<br />
Unused indexes<br />
SQL > SELECT object_name, index_name<br />
 FROM sys.schema_unused_indexes<br />
 WHERE object_schema = \'testtest\'<br />
;<br />
+------------------------+------------------------+<br />
&#124; object_name &#124; index_name &#124;<br />
+------------------------+------------------------+<br />
&#124; access &#124; customer_3 &#124;<br />
&#124; access &#124; customer_2 &#124;<br />
&#124; actions &#124; class &#124;<br />
&#124; actions &#124; action &#124;<br />
&#124; active &#124; channel &#124;<br />
...<br />
&#124; urls &#124; customer &#124;<br />
&#124; voucher_batches &#124; customer &#124;<br />
&#124; vouchers &#124; batch &#124;<br />
+------------------------+------------------------+<br />
413 rows in set<br />
Note:</p>
<p>For MariaDB, the PERFORMANCE_SCHEMA must be enabled first.<br />
The information is accurate as of the last database restart. If an index was last used BEFORE the most recent restart, it will be shown here as unused.</p>
<p>Source: Unused indexes<br />
And now with PostgreSQL<br />
Tables without a Primary Key<br />
SQL > SELECT tab.table_schema, tab.table_name<br />
 FROM information_schema.tables tab<br />
 LEFT JOIN information_schema.table_constraints tco<br />
 ON tab.table_schema = tco.table_schema<br />
 AND tab.table_name = tco.table_name<br />
 AND tco.constraint_type = \'PRIMARY KEY\'<br />
 WHERE tab.table_type = \'BASE TABLE\'<br />
 AND tab.table_schema NOT IN (\'pg_catalog\', \'information_schema\')<br />
 AND tco.constraint_name IS NULL<br />
 ORDER BY table_schema, table_name<br />
;<br />
 table_schema &#124; table_name<br />
--------------+------------<br />
 public &#124; archived<br />
(1 row)<br />
Source: Find tables without primary keys (PKs) in PostgreSQL database<br />
Duplicate indexes<br />
Based on the MySQL sys schema:<br />
SQL > WITH schema_flattened_keys AS (<br />
 SELECT sai.relid, sai.indexrelid<br />
 , sai.schemaname AS table_schema, sai.relname AS table_name, sai.indexrelname AS index_name<br />
 , CASE pi.indisunique WHEN \'f\' THEN 1 ELSE 0 END AS non_unique<br />
 , index_columns.columns AS index_columns<br />
 FROM pg_stat_all_indexes AS sai<br />
 JOIN pg_index AS pi ON pi.indexrelid = sai.indexrelid<br />
 JOIN (<br />
 SELECT attrelid, string_agg(attname, \',\' ORDER BY attnum ASC) AS columns<br />
 FROM pg_attribute GROUP BY attrelid<br />
 ) AS index_columns ON index_columns.attrelid = sai.indexrelid<br />
 WHERE sai.schemaname NOT IN (\'pg_toast\', \'pg_catalog\')<br />
)<br />
SELECT redundant_keys.table_schema AS table_schema, redundant_keys.table_name AS table_name, redundant_keys.index_name AS redundant_index_name<br />
 , redundant_keys.index_columns AS redundant_index_columns, redundant_keys.non_unique AS redundant_index_non_unique<br />
 , dominant_keys.index_name AS dominant_index_name, dominant_keys.index_columns AS dominant_index_columns, dominant_keys.non_unique AS dominant_index_non_unique<br />
 , CONCAT(\'ALTER TABLE \', redundant_keys.table_schema, \'.\', redundant_keys.table_name, \' DROP INDEX \', redundant_keys.index_name, \'\') AS sql_drop_index<br />
 FROM schema_flattened_keys redundant_keys<br />
 JOIN schema_flattened_keys dominant_keys ON redundant_keys.table_schema = dominant_keys.table_schema AND redundant_keys.table_name = dominant_keys.table_name<br />
 WHERE (redundant_keys.index_name < > dominant_keys.index_name<br />
 AND ((redundant_keys.index_columns = dominant_keys.index_columns)<br />
 AND ((redundant_keys.non_unique > dominant_keys.non_unique) OR (redundant_keys.non_unique = dominant_keys.non_unique))<br />
 )<br />
 OR ((POSITION(CONCAT(redundant_keys.index_columns,\',\') IN dominant_keys.index_columns) = 1) AND (redundant_keys.non_unique = 1))<br />
 OR ((POSITION(CONCAT(dominant_keys.index_columns,\',\') IN redundant_keys.index_columns) = 1) AND (dominant_keys.non_unique = 0))<br />
 )<br />
 AND redundant_keys.index_columns = dominant_keys.index_columns<br />
;<br />
 table_schema &#124; table_name &#124; redundant_index_name &#124; redundant_index_columns &#124; redundant_index_non_unique &#124; dominant_index_name &#124; dominant_index_columns &#124; dominant_index_non_unique &#124; sql_drop_index<br />
--------------+------------+----------------------+-------------------------+----------------------------+---------------------+------------------------+---------------------------+----------------------------------------------<br />
 public &#124; archived &#124; dupl1 &#124; category_id &#124; 1 &#124; dupl2 &#124; category_id &#124; 1 &#124; ALTER TABLE public.archived DROP INDEX dupl1<br />
 public &#124; archived &#124; dupl2 &#124; category_id &#124; 1 &#124; dupl1 &#124; category_id &#124; 1 &#124; ALTER TABLE public.archived DROP INDEX dupl2<br />
(2 rows)<br />
Source: Duplicate and redundant indices<br />
Partially redundant indexes<br />
Based on the MySQL sys schema:<br />
SQL > WITH schema_flattened_keys AS (<br />
 SELECT sai.relid, sai.indexrelid<br />
 , sai.schemaname AS table_schema, sai.relname AS table_name, sai.indexrelname AS index_name<br />
 , CASE pi.indisunique WHEN \'f\' THEN 1 ELSE 0 END AS non_unique<br />
 , index_columns.columns AS index_columns<br />
 FROM pg_stat_all_indexes AS sai<br />
 JOIN pg_index AS pi ON pi.indexrelid = sai.indexrelid<br />
 JOIN (<br />
 SELECT attrelid, string_agg(attname, \',\' ORDER BY attnum ASC) AS columns<br />
 FROM pg_attribute GROUP BY attrelid<br />
 ) AS index_columns ON index_columns.attrelid = sai.indexrelid<br />
 WHERE sai.schemaname NOT IN (\'pg_toast\', \'pg_catalog\')<br />
)<br />
SELECT redundant_keys.table_schema AS table_schema, redundant_keys.table_name AS table_name, redundant_keys.index_name AS redundant_index_name<br />
 , redundant_keys.index_columns AS redundant_index_columns, redundant_keys.non_unique AS redundant_index_non_unique<br />
 , dominant_keys.index_name AS dominant_index_name, dominant_keys.index_columns AS dominant_index_columns, dominant_keys.non_unique AS dominant_index_non_unique<br />
 , CONCAT(\'ALTER TABLE \', redundant_keys.table_schema, \'.\', redundant_keys.table_name, \' DROP INDEX \', redundant_keys.index_name, \'\') AS sql_drop_index<br />
 FROM schema_flattened_keys redundant_keys<br />
 JOIN schema_flattened_keys dominant_keys ON redundant_keys.table_schema = dominant_keys.table_schema AND redundant_keys.table_name = dominant_keys.table_name<br />
 WHERE (redundant_keys.index_name < > dominant_keys.index_name<br />
 AND ((redundant_keys.index_columns = dominant_keys.index_columns)<br />
 AND ((redundant_keys.non_unique > dominant_keys.non_unique) OR (redundant_keys.non_unique = dominant_keys.non_unique))<br />
 )<br />
 OR ((POSITION(CONCAT(redundant_keys.index_columns,\',\') IN dominant_keys.index_columns) = 1) AND (redundant_keys.non_unique = 1))<br />
 OR ((POSITION(CONCAT(dominant_keys.index_columns,\',\') IN redundant_keys.index_columns) = 1) AND (dominant_keys.non_unique = 0))<br />
 )<br />
;<br />
 table_schema &#124; table_name &#124; redundant_index_name &#124; redundant_index_columns &#124; redundant_index_non_unique &#124; dominant_index_name &#124; dominant_index_columns &#124; dominant_index_non_unique &#124; sql_drop_index<br />
--------------+-----------------------+------------------------------------------+-------------------------+----------------------------+-------------------------------------------------+-----------------------------------------+---------------------------+---------------------------------------------------------------------------------------------<br />
 public &#124; numbers &#124; numbers_customer_idx &#124; customer &#124; 1 &#124; numbers_customer_text_dtype_text_dnumber_idx &#124; customer,text_dtype,text_dnumber &#124; 1 &#124; ALTER TABLE public.numbers DROP INDEX numbers_customer_idx<br />
 public &#124; numbers &#124; numbers_customer_idx &#124; customer &#124; 1 &#124; numbers_customer_fax_dtype_fax_dnumber_idx &#124; customer,fax_dtype,fax_dnumber &#124; 1 &#124; ALTER TABLE public.numbers DROP INDEX numbers_customer_idx<br />
 public &#124; numbers &#124; numbers_customer_idx &#124; customer &#124; 1 &#124; numbers_pkey &#124; customer,stype,snumber &#124; 0 &#124; ALTER TABLE public.numbers DROP INDEX numbers_customer_idx<br />
 public &#124; numbers &#124; numbers_dtype_idx &#124; dtype &#124; 1 &#124; numbers_dtype_dnumber_idx &#124; dtype,dnumber &#124; 1 &#124; ALTER TABLE public.numbers DROP INDEX numbers_dtype_idx<br />
 public &#124; number_callers &#124; number_callers_dtype_idx &#124; dtype &#124; 1 &#124; number_callers_dtype_dnumber_idx &#124; dtype,dnumber &#124; 1 &#124; ALTER TABLE public.number_callers DROP INDEX number_callers_dtype_idx<br />
 public &#124; prefixes &#124; prefixes_customer_idx &#124; customer &#124; 1 &#124; prefixes_customer_dtype_dnumber_idx &#124; customer,dtype,dnumber &#124; 1 &#124; ALTER TABLE public.prefixes DROP INDEX prefixes_customer_idx<br />
 public &#124; number_times &#124; number_times_dtype_idx &#124; dtype &#124; 1 &#124; number_times_dtype_dnumber_idx &#124; dtype,dnumber &#124; 1 &#124; ALTER TABLE public.number_times DROP INDEX number_times_dtype_idx<br />
 public &#124; phones &#124; phones_customer_idx &#124; customer &#124; 1 &#124; phones_customer_callerid_location_idx &#124; customer,callerid_location &#124; 1 &#124; ALTER TABLE public.phones DROP INDEX phones_customer_idx<br />
 public &#124; phones &#124; phones_customer_idx &#124; customer &#124; 1 &#124; phones_customer_callerid_external_idx &#124; customer,callerid_external &#124; 1 &#124; ALTER TABLE public.phones DROP INDEX phones_customer_idx<br />
 public &#124; phones &#124; phones_customer_idx &#124; customer &#124; 1 &#124; phones_customer_callerid_internal_idx &#124; customer,callerid_internal &#124; 1 &#124; ALTER TABLE public.phones DROP INDEX phones_customer_idx<br />
 public &#124; phones_hardware &#124; phones_hardware_phone_idx &#124; phone &#124; 1 &#124; phones_hardware_phone_hardware_address_idx &#124; phone,hardware_address &#124; 0 &#124; ALTER TABLE public.phones_hardware DROP INDEX phones_hardware_phone_idx<br />
 public &#124; speeddials &#124; speeddials_stype_idx &#124; stype &#124; 1 &#124; speeddials_stype_snumber_idx &#124; stype,snumber &#124; 1 &#124; ALTER TABLE public.speeddials DROP INDEX speeddials_stype_idx<br />
 public &#124; speeddials &#124; speeddials_dtype_idx &#124; dtype &#124; 1 &#124; speeddials_dtype_dnumber_idx &#124; dtype,dnumber &#124; 1 &#124; ALTER TABLE public.speeddials DROP INDEX speeddials_dtype_idx<br />
 public &#124; mailbox_destinations &#124; mailbox_destinations_context_mailbox_idx &#124; context,mailbox &#124; 1 &#124; mailbox_destinations_pkey &#124; context,mailbox,dcustomer,dtype,dnumber &#124; 0 &#124; ALTER TABLE public.mailbox_destinations DROP INDEX mailbox_destinations_context_mailbox_idx<br />
 public &#124; outgroup_times &#124; outgroup_times_outgroup_idx &#124; outgroup &#124; 1 &#124; outgroup_times_outgroup_name_idx &#124; outgroup,name &#124; 0 &#124; ALTER TABLE public.outgroup_times DROP INDEX outgroup_times_outgroup_idx<br />
 public &#124; ingroup_times &#124; ingroup_times_ingroup_idx &#124; ingroup &#124; 1 &#124; ingroup_times_ingroup_name_idx &#124; ingroup,name &#124; 0 &#124; ALTER TABLE public.ingroup_times DROP INDEX ingroup_times_ingroup_idx<br />
 public &#124; active_customers &#124; active_customers_uniqueid_idx &#124; uniqueid &#124; 1 &#124; active_customers_pkey &#124; uniqueid,scustomer &#124; 0 &#124; ALTER TABLE public.active_customers DROP INDEX active_customers_uniqueid_idx<br />
 public &#124; access &#124; access_customer_idx &#124; customer &#124; 1 &#124; access_customer_callerid_external_idx &#124; customer,callerid_external &#124; 1 &#124; ALTER TABLE public.access DROP INDEX access_customer_idx<br />
 public &#124; access &#124; access_customer_idx &#124; customer &#124; 1 &#124; access_customer_callerid_internal_idx &#124; customer,callerid_internal &#124; 1 &#124; ALTER TABLE public.access DROP INDEX access_customer_idx<br />
 public &#124; unlimited_access &#124; unlimited_access_customer_idx &#124; customer &#124; 1 &#124; unlimited_access_customer_callerid_external_idx &#124; customer,callerid_external &#124; 1 &#124; ALTER TABLE public.unlimited_access DROP INDEX unlimited_access_customer_idx<br />
 public &#124; unlimited_access &#124; unlimited_access_customer_idx &#124; customer &#124; 1 &#124; unlimited_access_customer_callerid_internal_idx &#124; customer,callerid_internal &#124; 1 &#124; ALTER TABLE public.unlimited_access DROP INDEX unlimited_access_customer_idx<br />
 public &#124; texts &#124; texts_dcustomer_idx &#124; dcustomer &#124; 1 &#124; texts_dcustomer_dtype_dnumber_idx &#124; dcustomer,dtype,dnumber &#124; 1 &#124; ALTER TABLE public.texts DROP INDEX texts_dcustomer_idx<br />
 public &#124; texts_media &#124; texts_media_uniqueid_idx &#124; uniqueid &#124; 1 &#124; texts_media_pkey &#124; uniqueid,filename &#124; 0 &#124; ALTER TABLE public.texts_media DROP INDEX texts_media_uniqueid_idx<br />
 public &#124; number_calleridgroups &#124; number_calleridgroups_dtype_idx &#124; dtype &#124; 1 &#124; number_calleridgroups_dtype_dnumber_idx &#124; dtype,dnumber &#124; 1 &#124; ALTER TABLE public.number_calleridgroups DROP INDEX number_calleridgroups_dtype_idx<br />
 public &#124; analytics_include &#124; analytics_i &#124; analytics &#124; 1 &#124; analytics_include_pkey &#124; analytics,feature,dtype,dnumber &#124; 0 &#124; ALTER TABLE public.analytics_include DROP INDEX analytics_i<br />
 public &#124; archived &#124; dupl1 &#124; category_id &#124; 1 &#124; dupl2 &#124; category_id &#124; 1 &#124; ALTER TABLE public.archived DROP INDEX dupl1<br />
 public &#124; archived &#124; dupl2 &#124; category_id &#124; 1 &#124; dupl1 &#124; category_id &#124; 1 &#124; ALTER TABLE public.archived DROP INDEX dupl2<br />
(27 rows)<br />
Source: Duplicate and redundant indices<br />
Unused indexes<br />
SQL > SELECT relid::regclass AS table, indexrelid::regclass AS index<br />
 , pg_size_pretty(pg_relation_size(indexrelid::regclass)) AS index_size<br />
 , idx_tup_read, idx_tup_fetch, idx_scan<br />
 FROM pg_stat_user_indexes<br />
 JOIN pg_index USING (indexrelid)<br />
 WHERE idx_scan = 0<br />
 AND indisunique IS FALSE<br />
;<br />
 table &#124; index &#124; index_size &#124; idx_tup_read &#124; idx_tup_fetch &#124; idx_scan<br />
------------------------+-----------------------------------------------------------------+------------+--------------+---------------+----------<br />
 customers &#124; customers_prefix_idx &#124; 16 kB &#124; 0 &#124; 0 &#124; 0<br />
 customers &#124; customers_parent_idx &#124; 16 kB &#124; 0 &#124; 0 &#124; 0<br />
 customers &#124; customers_email_idx &#124; 16 kB &#124; 0 &#124; 0 &#124; 0<br />
 customers &#124; customers_affiliate_customer_idx &#124; 16 kB &#124; 0 &#124; 0 &#124; 0<br />
 customers &#124; customers_bill_ref_idx &#124; 16 kB &#124; 0 &#124; 0 &#124; 0<br />
...<br />
 analytics_include &#124; analytics_i &#124; 8192 bytes &#124; 0 &#124; 0 &#124; 0<br />
 archived &#124; dupl1 &#124; 8192 bytes &#124; 0 &#124; 0 &#124; 0<br />
 archived &#124; dupl2 &#124; 8192 bytes &#124; 0 &#124; 0 &#124; 0<br />
(413 rows)<br />
Sources:</p>
<p>Unused Indexes<br />
Postgresql: Monitor unused indexes</p>
<p>PG Assistant<br />
At the Swiss PGDay2026(s), Bertrand Hartwig presented his tool PG Assistant. In that context, I wanted to try it out right away…<br />
PG Assistant was able to find missing Primary Keys and duplicate indexes. It didn’t show me any partially redundant or unused indexes, but that might just be on my end…</p>
<p> PG Assistant: Dashboard / Dev advisor</p>
<p> PG Assistant: Global Advisor / Dev advisor</p>
<p> PG Assistant: Strictly duplicate unused index</p>
<p>Intallation of PG Assistant<br />
$ apt update<br />
$ apt install python3 python3.13-venv unzip pip<br />
$ wget https://github.com/beh74/pgassistant-community/archive/refs/heads/main.zip<br />
$ unzip main.zip<br />
$ cd pgassistant-community-main/<br />
$ python3 -m venv env<br />
$ source env/bin/activate<br />
$ pip3 install -r requirements.txt<br />
$ export FLASK_APP=run.py<br />
$ flask run --host=0.0.0.0 --port=80<br />
Then connect to the displayed URL using a web browser.<br />
A user must be created in the database first:<br />
SQL > CREATE ROLE pgassistant WITH LOGIN SUPERUSER PASSWORD \'secret\';<br />
and the pg_hba.conf file must be adapted.<br />
Addendum<br />
You can find unused indexes using the PG Assistant as follows: Database Objects ➜ Indexes ➜ Status: Unused ➜ “NO INDEX ACTIVITY”</p>
<p> PG Assistant: Unused Indexes</p>
<p><a href="https://www.fromdual.com/blog/database_index_optimizer/">Database Index Optimizer</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Recently, a client asked me if the &ldquo;time-consuming&rdquo; task of checking indexes could be left to an index optimizer. Of course it can&hellip;</p>
<p>What exactly do we want to check?</p>
<ul>
<li>Tables without a Primary Key</li>
<li>Duplicate indexes</li>
<li>Partially redundant indexes</li>
<li>Unused indexes</li>
</ul>
<h2 id="mariadb-mysql-and-percona-server">MariaDB, MySQL, and Percona Server<a class="anchor-link" id="mariadb-mysql-and-percona-server"></a></h2>
<h3 id="tables-without-a-primary-key">Tables without a Primary Key<a class="anchor-link" id="tables-without-a-primary-key"></a></h3>
<pre><code>SQL&gt; SELECT DISTINCT t.table_schema, t.table_name
 FROM information_schema.tables AS t
 LEFT JOIN information_schema.columns AS c ON t.table_schema = c.table_schema AND t.table_name = c.table_name
 AND c.column_key = "PRI"
 WHERE t.table_schema NOT IN ('information_schema', 'mysql', 'performance_schema')
 AND c.table_name IS NULL AND t.table_type NOT IN('VIEW', 'SEQUENCE')
 AND t.table_schema = 'testtest'
;
+--------------+------------+
| table_schema | table_name |
+--------------+------------+
| testtest | archived |
+--------------+------------+
1 row in set
</code></pre>
<p>Source: <a href="https://www.fromdual.com/blog/mysql-performance-schema-hints/#tables-without-primary-key" target="_blank" rel="noopener">Tables without a Primary Key</a></p>
<h3 id="duplicate-indexes">Duplicate indexes<a class="anchor-link" id="duplicate-indexes"></a></h3>
<pre><code>SQL&gt; SELECT table_name, redundant_index_name, redundant_index_columns, dominant_index_name, dominant_index_columns, sql_drop_index
 FROM sys.schema_redundant_indexes
 WHERE redundant_index_columns = dominant_index_columns
 AND table_schema = 'testtest'
;
+------------+----------------------+-------------------------+---------------------+------------------------+------------------------------------------------------+
| table_name | redundant_index_name | redundant_index_columns | dominant_index_name | dominant_index_columns | sql_drop_index |
+------------+----------------------+-------------------------+---------------------+------------------------+------------------------------------------------------+
| archived | dupl2 | category_id | dupl1 | category_id | ALTER TABLE `testtest`.`archived` DROP INDEX `dupl2` |
+------------+----------------------+-------------------------+---------------------+------------------------+------------------------------------------------------+
1 row in set
</code></pre>
<p>Source: <a href="https://www.fromdual.com/blog/mysql-performance-schema-hints/#duplicate-and-redundant-indices" target="_blank" rel="noopener">Duplicate and redundant indices</a></p>
<h3 id="partially-redundant-indexes">Partially redundant indexes<a class="anchor-link" id="partially-redundant-indexes"></a></h3>
<pre><code>SQL&gt; SELECT table_name, redundant_index_name, redundant_index_columns, dominant_index_name, dominant_index_columns, sql_drop_index
 FROM sys.schema_redundant_indexes
 WHERE table_schema = 'testtest'
;
+-------------------+----------------------+-------------------------+---------------------+---------------------------------+-------------------------------------------------------------------+
| table_name | redundant_index_name | redundant_index_columns | dominant_index_name | dominant_index_columns | sql_drop_index |
+-------------------+----------------------+-------------------------+---------------------+---------------------------------+-------------------------------------------------------------------+
| access | customer | customer | customer_2 | customer,callerid_internal | ALTER TABLE `testtest`.`access` DROP INDEX `customer` |
| access | customer | customer | customer_3 | customer,callerid_external | ALTER TABLE `testtest`.`access` DROP INDEX `customer` |
| active_customers | uniqueid | uniqueid | PRIMARY | uniqueid,scustomer | ALTER TABLE `testtest`.`active_customers` DROP INDEX `uniqueid` |
| analytics_include | analytics | analytics | PRIMARY | analytics,feature,dtype,dnumber | ALTER TABLE `testtest`.`analytics_include` DROP INDEX `analytics` |
| archived | dupl2 | category_id | dupl1 | category_id | ALTER TABLE `testtest`.`archived` DROP INDEX `dupl2` |
...
| texts_media | uniqueid | uniqueid | PRIMARY | uniqueid,filename | ALTER TABLE `testtest`.`texts_media` DROP INDEX `uniqueid` |
| unlimited_access | customer | customer | customer_2 | customer,callerid_internal | ALTER TABLE `testtest`.`unlimited_access` DROP INDEX `customer` |
| unlimited_access | customer | customer | customer_3 | customer,callerid_external | ALTER TABLE `testtest`.`unlimited_access` DROP INDEX `customer` |
+-------------------+----------------------+-------------------------+---------------------+---------------------------------+-------------------------------------------------------------------+
26 rows in set
</code></pre>
<p>Source: <a href="https://www.fromdual.com/blog/mysql-performance-schema-hints/#duplicate-and-redundant-indices" target="_blank" rel="noopener">Duplicate and redundant indices</a></p>
<h3 id="unused-indexes">Unused indexes<a class="anchor-link" id="unused-indexes"></a></h3>
<pre><code>SQL&gt; SELECT object_name, index_name
 FROM sys.schema_unused_indexes
 WHERE object_schema = 'testtest'
;
+------------------------+------------------------+
| object_name | index_name |
+------------------------+------------------------+
| access | customer_3 |
| access | customer_2 |
| actions | class |
| actions | action |
| active | channel |
...
| urls | customer |
| voucher_batches | customer |
| vouchers | batch |
+------------------------+------------------------+
413 rows in set
</code></pre>
<p><strong>Note</strong>:</p>
<ul>
<li>For MariaDB, the <code>PERFORMANCE_SCHEMA</code> must be enabled first.</li>
<li>The information is accurate as of the last database restart. If an index was last used BEFORE the most recent restart, it will be shown here as unused.</li>
</ul>
<p>Source: <a href="https://www.fromdual.com/blog/mysql-performance-schema-hints/#unused-indexes" target="_blank" rel="noopener">Unused indexes</a></p>
<h2 id="and-now-with-postgresql">And now with PostgreSQL<a class="anchor-link" id="and-now-with-postgresql"></a></h2>
<h3 id="tables-without-a-primary-key-1">Tables without a Primary Key<a class="anchor-link" id="tables-without-a-primary-key"></a></h3>
<pre><code>SQL&gt; SELECT tab.table_schema, tab.table_name
 FROM information_schema.tables tab
 LEFT JOIN information_schema.table_constraints tco
 ON tab.table_schema = tco.table_schema
 AND tab.table_name = tco.table_name 
 AND tco.constraint_type = 'PRIMARY KEY'
 WHERE tab.table_type = 'BASE TABLE'
 AND tab.table_schema NOT IN ('pg_catalog', 'information_schema')
 AND tco.constraint_name IS NULL
 ORDER BY table_schema, table_name
;
 table_schema | table_name 
--------------+------------
 public | archived
(1 row)
</code></pre>
<p>Source: <a href="https://dataedo.com/kb/query/postgresql/find-tables-without-primary-keys" target="_blank" rel="noopener">Find tables without primary keys (PKs) in PostgreSQL database</a></p>
<h3 id="duplicate-indexes-1">Duplicate indexes<a class="anchor-link" id="duplicate-indexes"></a></h3>
<p>Based on the MySQL <code>sys</code> schema:</p>
<pre><code>SQL&gt; WITH schema_flattened_keys AS (
 SELECT sai.relid, sai.indexrelid
 , sai.schemaname AS table_schema, sai.relname AS table_name, sai.indexrelname AS index_name
 , CASE pi.indisunique WHEN 'f' THEN 1 ELSE 0 END AS non_unique
 , index_columns.columns AS index_columns
 FROM pg_stat_all_indexes AS sai
 JOIN pg_index AS pi ON pi.indexrelid = sai.indexrelid
 JOIN (
 SELECT attrelid, string_agg(attname, ',' ORDER BY attnum ASC) AS columns
 FROM pg_attribute GROUP BY attrelid
 ) AS index_columns ON index_columns.attrelid = sai.indexrelid
 WHERE sai.schemaname NOT IN ('pg_toast', 'pg_catalog')
)
SELECT redundant_keys.table_schema AS table_schema, redundant_keys.table_name AS table_name, redundant_keys.index_name AS redundant_index_name
 , redundant_keys.index_columns AS redundant_index_columns, redundant_keys.non_unique AS redundant_index_non_unique
 , dominant_keys.index_name AS dominant_index_name, dominant_keys.index_columns AS dominant_index_columns, dominant_keys.non_unique AS dominant_index_non_unique
 , CONCAT('ALTER TABLE ', redundant_keys.table_schema, '.', redundant_keys.table_name, ' DROP INDEX ', redundant_keys.index_name, '') AS sql_drop_index
 FROM schema_flattened_keys redundant_keys
 JOIN schema_flattened_keys dominant_keys ON redundant_keys.table_schema = dominant_keys.table_schema AND redundant_keys.table_name = dominant_keys.table_name
 WHERE (redundant_keys.index_name &lt;&gt; dominant_keys.index_name
 AND ((redundant_keys.index_columns = dominant_keys.index_columns)
 AND ((redundant_keys.non_unique &gt; dominant_keys.non_unique) OR (redundant_keys.non_unique = dominant_keys.non_unique))
 )
 OR ((POSITION(CONCAT(redundant_keys.index_columns,',') IN dominant_keys.index_columns) = 1) AND (redundant_keys.non_unique = 1))
 OR ((POSITION(CONCAT(dominant_keys.index_columns,',') IN redundant_keys.index_columns) = 1) AND (dominant_keys.non_unique = 0))
 )
 AND redundant_keys.index_columns = dominant_keys.index_columns
;
 table_schema | table_name | redundant_index_name | redundant_index_columns | redundant_index_non_unique | dominant_index_name | dominant_index_columns | dominant_index_non_unique | sql_drop_index 
--------------+------------+----------------------+-------------------------+----------------------------+---------------------+------------------------+---------------------------+----------------------------------------------
 public | archived | dupl1 | category_id | 1 | dupl2 | category_id | 1 | ALTER TABLE public.archived DROP INDEX dupl1
 public | archived | dupl2 | category_id | 1 | dupl1 | category_id | 1 | ALTER TABLE public.archived DROP INDEX dupl2
(2 rows)
</code></pre>
<p>Source: <a href="https://www.fromdual.com/blog/mysql-performance-schema-hints/#duplicate-and-redundant-indices" target="_blank" rel="noopener">Duplicate and redundant indices</a></p>
<h3 id="partially-redundant-indexes-1">Partially redundant indexes<a class="anchor-link" id="partially-redundant-indexes"></a></h3>
<p>Based on the MySQL <code>sys</code> schema:</p>
<pre><code>SQL&gt; WITH schema_flattened_keys AS (
 SELECT sai.relid, sai.indexrelid
 , sai.schemaname AS table_schema, sai.relname AS table_name, sai.indexrelname AS index_name
 , CASE pi.indisunique WHEN 'f' THEN 1 ELSE 0 END AS non_unique
 , index_columns.columns AS index_columns
 FROM pg_stat_all_indexes AS sai
 JOIN pg_index AS pi ON pi.indexrelid = sai.indexrelid
 JOIN (
 SELECT attrelid, string_agg(attname, ',' ORDER BY attnum ASC) AS columns
 FROM pg_attribute GROUP BY attrelid
 ) AS index_columns ON index_columns.attrelid = sai.indexrelid
 WHERE sai.schemaname NOT IN ('pg_toast', 'pg_catalog')
)
SELECT redundant_keys.table_schema AS table_schema, redundant_keys.table_name AS table_name, redundant_keys.index_name AS redundant_index_name
 , redundant_keys.index_columns AS redundant_index_columns, redundant_keys.non_unique AS redundant_index_non_unique
 , dominant_keys.index_name AS dominant_index_name, dominant_keys.index_columns AS dominant_index_columns, dominant_keys.non_unique AS dominant_index_non_unique
 , CONCAT('ALTER TABLE ', redundant_keys.table_schema, '.', redundant_keys.table_name, ' DROP INDEX ', redundant_keys.index_name, '') AS sql_drop_index
 FROM schema_flattened_keys redundant_keys
 JOIN schema_flattened_keys dominant_keys ON redundant_keys.table_schema = dominant_keys.table_schema AND redundant_keys.table_name = dominant_keys.table_name
 WHERE (redundant_keys.index_name &lt;&gt; dominant_keys.index_name
 AND ((redundant_keys.index_columns = dominant_keys.index_columns)
 AND ((redundant_keys.non_unique &gt; dominant_keys.non_unique) OR (redundant_keys.non_unique = dominant_keys.non_unique))
 )
 OR ((POSITION(CONCAT(redundant_keys.index_columns,',') IN dominant_keys.index_columns) = 1) AND (redundant_keys.non_unique = 1))
 OR ((POSITION(CONCAT(dominant_keys.index_columns,',') IN redundant_keys.index_columns) = 1) AND (dominant_keys.non_unique = 0))
 )
;
 table_schema | table_name | redundant_index_name | redundant_index_columns | redundant_index_non_unique | dominant_index_name | dominant_index_columns | dominant_index_non_unique | sql_drop_index 
--------------+-----------------------+------------------------------------------+-------------------------+----------------------------+-------------------------------------------------+-----------------------------------------+---------------------------+---------------------------------------------------------------------------------------------
 public | numbers | numbers_customer_idx | customer | 1 | numbers_customer_text_dtype_text_dnumber_idx | customer,text_dtype,text_dnumber | 1 | ALTER TABLE public.numbers DROP INDEX numbers_customer_idx
 public | numbers | numbers_customer_idx | customer | 1 | numbers_customer_fax_dtype_fax_dnumber_idx | customer,fax_dtype,fax_dnumber | 1 | ALTER TABLE public.numbers DROP INDEX numbers_customer_idx
 public | numbers | numbers_customer_idx | customer | 1 | numbers_pkey | customer,stype,snumber | 0 | ALTER TABLE public.numbers DROP INDEX numbers_customer_idx
 public | numbers | numbers_dtype_idx | dtype | 1 | numbers_dtype_dnumber_idx | dtype,dnumber | 1 | ALTER TABLE public.numbers DROP INDEX numbers_dtype_idx
 public | number_callers | number_callers_dtype_idx | dtype | 1 | number_callers_dtype_dnumber_idx | dtype,dnumber | 1 | ALTER TABLE public.number_callers DROP INDEX number_callers_dtype_idx
 public | prefixes | prefixes_customer_idx | customer | 1 | prefixes_customer_dtype_dnumber_idx | customer,dtype,dnumber | 1 | ALTER TABLE public.prefixes DROP INDEX prefixes_customer_idx
 public | number_times | number_times_dtype_idx | dtype | 1 | number_times_dtype_dnumber_idx | dtype,dnumber | 1 | ALTER TABLE public.number_times DROP INDEX number_times_dtype_idx
 public | phones | phones_customer_idx | customer | 1 | phones_customer_callerid_location_idx | customer,callerid_location | 1 | ALTER TABLE public.phones DROP INDEX phones_customer_idx
 public | phones | phones_customer_idx | customer | 1 | phones_customer_callerid_external_idx | customer,callerid_external | 1 | ALTER TABLE public.phones DROP INDEX phones_customer_idx
 public | phones | phones_customer_idx | customer | 1 | phones_customer_callerid_internal_idx | customer,callerid_internal | 1 | ALTER TABLE public.phones DROP INDEX phones_customer_idx
 public | phones_hardware | phones_hardware_phone_idx | phone | 1 | phones_hardware_phone_hardware_address_idx | phone,hardware_address | 0 | ALTER TABLE public.phones_hardware DROP INDEX phones_hardware_phone_idx
 public | speeddials | speeddials_stype_idx | stype | 1 | speeddials_stype_snumber_idx | stype,snumber | 1 | ALTER TABLE public.speeddials DROP INDEX speeddials_stype_idx
 public | speeddials | speeddials_dtype_idx | dtype | 1 | speeddials_dtype_dnumber_idx | dtype,dnumber | 1 | ALTER TABLE public.speeddials DROP INDEX speeddials_dtype_idx
 public | mailbox_destinations | mailbox_destinations_context_mailbox_idx | context,mailbox | 1 | mailbox_destinations_pkey | context,mailbox,dcustomer,dtype,dnumber | 0 | ALTER TABLE public.mailbox_destinations DROP INDEX mailbox_destinations_context_mailbox_idx
 public | outgroup_times | outgroup_times_outgroup_idx | outgroup | 1 | outgroup_times_outgroup_name_idx | outgroup,name | 0 | ALTER TABLE public.outgroup_times DROP INDEX outgroup_times_outgroup_idx
 public | ingroup_times | ingroup_times_ingroup_idx | ingroup | 1 | ingroup_times_ingroup_name_idx | ingroup,name | 0 | ALTER TABLE public.ingroup_times DROP INDEX ingroup_times_ingroup_idx
 public | active_customers | active_customers_uniqueid_idx | uniqueid | 1 | active_customers_pkey | uniqueid,scustomer | 0 | ALTER TABLE public.active_customers DROP INDEX active_customers_uniqueid_idx
 public | access | access_customer_idx | customer | 1 | access_customer_callerid_external_idx | customer,callerid_external | 1 | ALTER TABLE public.access DROP INDEX access_customer_idx
 public | access | access_customer_idx | customer | 1 | access_customer_callerid_internal_idx | customer,callerid_internal | 1 | ALTER TABLE public.access DROP INDEX access_customer_idx
 public | unlimited_access | unlimited_access_customer_idx | customer | 1 | unlimited_access_customer_callerid_external_idx | customer,callerid_external | 1 | ALTER TABLE public.unlimited_access DROP INDEX unlimited_access_customer_idx
 public | unlimited_access | unlimited_access_customer_idx | customer | 1 | unlimited_access_customer_callerid_internal_idx | customer,callerid_internal | 1 | ALTER TABLE public.unlimited_access DROP INDEX unlimited_access_customer_idx
 public | texts | texts_dcustomer_idx | dcustomer | 1 | texts_dcustomer_dtype_dnumber_idx | dcustomer,dtype,dnumber | 1 | ALTER TABLE public.texts DROP INDEX texts_dcustomer_idx
 public | texts_media | texts_media_uniqueid_idx | uniqueid | 1 | texts_media_pkey | uniqueid,filename | 0 | ALTER TABLE public.texts_media DROP INDEX texts_media_uniqueid_idx
 public | number_calleridgroups | number_calleridgroups_dtype_idx | dtype | 1 | number_calleridgroups_dtype_dnumber_idx | dtype,dnumber | 1 | ALTER TABLE public.number_calleridgroups DROP INDEX number_calleridgroups_dtype_idx
 public | analytics_include | analytics_i | analytics | 1 | analytics_include_pkey | analytics,feature,dtype,dnumber | 0 | ALTER TABLE public.analytics_include DROP INDEX analytics_i
 public | archived | dupl1 | category_id | 1 | dupl2 | category_id | 1 | ALTER TABLE public.archived DROP INDEX dupl1
 public | archived | dupl2 | category_id | 1 | dupl1 | category_id | 1 | ALTER TABLE public.archived DROP INDEX dupl2
(27 rows)
</code></pre>
<p>Source: <a href="https://www.fromdual.com/blog/mysql-performance-schema-hints/#duplicate-and-redundant-indices" target="_blank" rel="noopener">Duplicate and redundant indices</a></p>
<h3 id="unused-indexes-1">Unused indexes<a class="anchor-link" id="unused-indexes"></a></h3>
<pre><code>SQL&gt; SELECT relid::regclass AS table, indexrelid::regclass AS index
 , pg_size_pretty(pg_relation_size(indexrelid::regclass)) AS index_size
 , idx_tup_read, idx_tup_fetch, idx_scan
 FROM pg_stat_user_indexes 
 JOIN pg_index USING (indexrelid) 
 WHERE idx_scan = 0 
 AND indisunique IS FALSE
;
 table | index | index_size | idx_tup_read | idx_tup_fetch | idx_scan 
------------------------+-----------------------------------------------------------------+------------+--------------+---------------+----------
 customers | customers_prefix_idx | 16 kB | 0 | 0 | 0
 customers | customers_parent_idx | 16 kB | 0 | 0 | 0
 customers | customers_email_idx | 16 kB | 0 | 0 | 0
 customers | customers_affiliate_customer_idx | 16 kB | 0 | 0 | 0
 customers | customers_bill_ref_idx | 16 kB | 0 | 0 | 0
...
 analytics_include | analytics_i | 8192 bytes | 0 | 0 | 0
 archived | dupl1 | 8192 bytes | 0 | 0 | 0
 archived | dupl2 | 8192 bytes | 0 | 0 | 0
(413 rows)
</code></pre>
<p>Sources:</p>
<ul>
<li><a href="https://wiki.postgresql.org/wiki/Index_Maintenance#Unused_Indexes" target="_blank" rel="noopener">Unused Indexes</a></li>
<li><a href="https://jmorano.moretrix.com/2014/02/postgresql-monitor-unused-indexes/" target="_blank" rel="noopener">Postgresql: Monitor unused indexes</a></li>
</ul>
<h3 id="pg-assistant">PG Assistant<a class="anchor-link" id="pg-assistant"></a></h3>
<p>At the <a href="https://2026.pgday.ch/schedule/" target="_blank" rel="noopener">Swiss PGDay2026</a>(s), Bertrand Hartwig presented his tool <a href="https://github.com/beh74/pgassistant-community" target="_blank" rel="noopener">PG Assistant</a>. In that context, I wanted to try it out right away&hellip;</p>
<p>PG Assistant was able to find missing Primary Keys and duplicate indexes. It didn&rsquo;t show me any partially redundant or unused indexes, but that might just be on my end&hellip;</p>
<figure>
 <a href="https://www.fromdual.com/images/pgAssistant_Screenshot_20260715_111018.png" title="full size"><img decoding="async" src="https://www.fromdual.com/images/pgAssistant_Screenshot_20260715_111018_640x509.png" alt="pgAssistant-1"></a><figcaption>PG Assistant: Dashboard / Dev advisor</figcaption></figure>
<p></p>
<figure>
 <a href="https://www.fromdual.com/images/pgAssistant_Screenshot_20260715_111135.png" title="full size"><img decoding="async" src="https://www.fromdual.com/images/pgAssistant_Screenshot_20260715_111135_640x533.png" alt="pgAssistant-2"></a><figcaption>PG Assistant: Global Advisor / Dev advisor</figcaption></figure>
<p></p>
<figure>
 <a href="https://www.fromdual.com/images/pgAssistant_Screenshot_20260715_111230.png" title="full size"><img decoding="async" src="https://www.fromdual.com/images/pgAssistant_Screenshot_20260715_111230_640x532.png" alt="pgAssistant-3"></a><figcaption>PG Assistant: Strictly duplicate unused index</figcaption></figure>
<p></p>
<h4 id="intallation-of-pg-assistant">Intallation of PG Assistant</h4>
<pre><code>$ apt update
$ apt install python3 python3.13-venv unzip pip
$ wget https://github.com/beh74/pgassistant-community/archive/refs/heads/main.zip
$ unzip main.zip 
$ cd pgassistant-community-main/
$ python3 -m venv env
$ source env/bin/activate
$ pip3 install -r requirements.txt
$ export FLASK_APP=run.py
$ flask run --host=0.0.0.0 --port=80
</code></pre>
<p>Then connect to the displayed URL using a web browser.</p>
<p>A user must be created in the database first:</p>
<pre><code>SQL&gt; CREATE ROLE pgassistant WITH LOGIN SUPERUSER PASSWORD 'secret';
</code></pre>
<p>and the <code>pg_hba.conf</code> file must be adapted.</p>
<h2 id="addendum">Addendum<a class="anchor-link" id="addendum"></a></h2>
<p>You can find unused indexes using the PG Assistant as follows: Database Objects &#10140; Indexes &#10140; Status: Unused &#10140; &ldquo;NO INDEX ACTIVITY&rdquo;</p>
<figure>
 <a href="https://www.fromdual.com/images/pgAssistant_Screenshot_20260716_093730.png" title="volle Gr&ouml;sse"><img decoding="async" src="https://www.fromdual.com/images/pgAssistant_Screenshot_20260716_093730_640x459.png" alt="pgAssistant-4"></a><figcaption>PG Assistant: Unused Indexes</figcaption></figure>
<p></p>

<p><a href="https://www.fromdual.com/blog/database_index_optimizer/">Database Index Optimizer</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>PostgreSQL Meta Commands that save time every day</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/postgresql-meta-commands-that-save-time-every-day/" />
      <id>https://www.percona.com/blog/postgresql-meta-commands-that-save-time-every-day/</id>
      <updated>2026-07-15T12:44:13+00:00</updated>
      <author><name>Sonia Valeja</name></author>
      <summary type="html"><![CDATA[<p>When most people start working with PostgreSQL, they quickly learn SQL: [crayon-6a57891fbdcdf304804307/] But very soon, another world opens up inside psql — a set of commands that don’t look like SQL, don’t end with semicolons. These are PostgreSQL Meta Commands, and they quietly power the daily workflow of almost every experienced DBA. Meta commands are … Continued<br />
The post PostgreSQL Meta Commands that save time every day appeared first on Percona.</p>
<p><a href="https://www.percona.com/blog/postgresql-meta-commands-that-save-time-every-day/">PostgreSQL Meta Commands that save time every day</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">When most people start working with PostgreSQL, they quickly learn SQL:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">SELECT * FROM employees;</pre>
<p><span style="font-weight: 400">But very soon, another world opens up inside </span><span style="font-weight: 400">psql</span><span style="font-weight: 400"> &mdash; a set of commands that don&rsquo;t look like SQL, don&rsquo;t end with semicolons.</span></p>
<p><span style="font-weight: 400">These are </span><b>PostgreSQL Meta Commands</b><span style="font-weight: 400">, and they quietly power the daily workflow of almost every experienced DBA.</span></p>
<p><span style="font-weight: 400">Meta commands are not about querying data &mdash; they are about </span><b>navigating, inspecting, and controlling the PostgreSQL session/database efficiently</b><span style="font-weight: 400">.</span></p>
<h3><span style="font-weight: 400">What exactly are Meta Commands?</span><a class="anchor-link" id="what-exactly-are-meta-commands"></a></h3>
<p><span style="font-weight: 400">Meta commands are special instructions interpreted by </span><span style="font-weight: 400">psql</span><span style="font-weight: 400">, not PostgreSQL itself.</span></p>
<p><span style="font-weight: 400">That means:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">They are </span><b>not SQL</b></li>
<li style="font-weight: 400"><span style="font-weight: 400">They execute instantly on the client side</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">They are specific to the </span><span style="font-weight: 400">psql</span><span style="font-weight: 400"> terminal tool</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">They do not end with semicolon like SQL statements</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">The main focus area for meta commands is database interaction and not the interaction with the data in the database.</span></li>
</ul>
<h3><strong>Cheat Sheet (Quick Reference)&nbsp;</strong><a class="anchor-link" id="cheat-sheet-quick-reference"></a></h3>
<p><span style="font-weight: 400">The most commonly used meta commands are as follows. There are many more apart from these, however, below are the most frequently used ones:</span></p>
<h3><span style="font-weight: 400">Connect and Manage Sessions</span><a class="anchor-link" id="connect-and-manage-sessions"></a></h3>
<p><span style="font-weight: 400">These commands help discover databases, establish connections, and verify the current session.</span></p>
<table style="height: 33px" border="1" width="701">
<tbody>
<tr>
<td><span style="font-weight: 400">c</span></td>
<td><span style="font-weight: 400">Connect to another database&nbsp;</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">l</span></td>
<td><span style="font-weight: 400">List all the databases available in the cluster</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">l+</span></td>
<td><span style="font-weight: 400">List all the databases available in the cluster with more details, like DB Size, etc</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">conninfo</span></td>
<td><span style="font-weight: 400">Displays information about the current database connection</span></td>
</tr>
</tbody>
</table>
<p><span style="font-weight: 400">Please find the example of the commands used to connect and manage sessions in the screenshot below:</span></p>
<p><img loading="lazy" decoding="async" class="alignnone size-medium wp-image-50262" src="https://www.percona.com/wp-content/uploads/2026/07/Screenshot-2026-07-15-at-11.39.10-AM-300x134.png" alt="" width="300" height="134"></p>
<h3><span style="font-weight: 400">Inspect Database Objects </span><span style="font-weight: 400">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><a class="anchor-link" id="inspect-database-objects"></a></h3>
<p><span style="font-weight: 400">The</span><span style="font-weight: 400"><br>
			<span id="urvanov-syntax-highlighter-6a57891fbdce5734642619" 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-sy"></span><span class="crayon-v">d</span></span></span>&nbsp;</span><span style="font-weight: 400"> family of commands is one of the most powerful features of<br>
			<span id="urvanov-syntax-highlighter-6a57891fbdce8854379316" 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">psql</span></span></span>&nbsp;</span><span style="font-weight: 400">. These commands can be used to discover database objects, inspect their definitions, and view additional metadata.</span></p>
<table border="1">
<tbody>
<tr>
<td><span style="font-weight: 400">d</span></td>
<td><span style="font-weight: 400">Describe database objects or list objects visible in the current search path.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">d object_name</span></td>
<td><span style="font-weight: 400">Describe a specific table, view, sequence, or other database object.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">d+ object_name</span></td>
<td><span style="font-weight: 400">Display extended information about an object.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">dt</span></td>
<td><span style="font-weight: 400">List tables. Supports schema names and wildcard patterns.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">di</span></td>
<td><span style="font-weight: 400">List indexes. Supports wildcard patterns.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">dn</span></td>
<td><span style="font-weight: 400">List schemas in the current database.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">du</span></td>
<td><span style="font-weight: 400">List database roles.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">db</span></td>
<td><span style="font-weight: 400">List tablespaces</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">dx</span></td>
<td><span style="font-weight: 400">List installed extensions</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">df</span></td>
<td><span style="font-weight: 400">List functions and procedures</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">sf function name</span></td>
<td><span style="font-weight: 400">Displays the source code of the specific function/procedure</span></td>
</tr>
</tbody>
</table>
<h4></h4>
<h4><span style="font-weight: 400">Using object names and wildcards</span></h4>
<p><span style="font-weight: 400">Most object-inspection commands accept object names, schema-qualified names, and wildcard patterns.</span></p>
<p><span style="font-weight: 400">For example:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">dt</pre>
<p><span style="font-weight: 400">Lists all tables in the current search path.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">dt public.*</pre>
<p><span style="font-weight: 400">Lists all tables in the public schema.</span></p>
<p><span style="font-weight: 400">The same pattern matching is supported by several other meta-commands, including </span><span style="font-weight: 400">di, df,</span><span style="font-weight: 400"> and the</span><span style="font-weight: 400"> d</span><span style="font-weight: 400"> family.</span></p>
<p><span style="font-weight: 400">Please find the example of the </span><span style="font-weight: 400">d </span><span style="font-weight: 400">family</span> <span style="font-weight: 400">commands in the screenshot below:</span></p>
<p><img loading="lazy" decoding="async" class="alignnone size-medium wp-image-50263" src="https://www.percona.com/wp-content/uploads/2026/07/Screenshot-2026-07-15-at-11.43.18-AM-300x103.png" alt="" width="300" height="103"></p>
<h3><span style="font-weight: 400">Format Query Results</span><a class="anchor-link" id="format-query-results"></a></h3>
<p><span style="font-weight: 400">Several meta-commands are available to improve the readability of query output, particularly when working with wide result sets.</span></p>
<table border="1">
<tbody>
<tr>
<td><span style="font-weight: 400">x [on|off|auto]</span></td>
<td><span style="font-weight: 400">Toggle expanded (vertical) display</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">o filename</span></td>
<td><span style="font-weight: 400">Redirect query output to a file or pipe.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">o</span></td>
<td><span style="font-weight: 400">Restore query output to the terminal.</span></td>
</tr>
</tbody>
</table>
<h3><a class="anchor-link" id=""></a></h3>
<h3><span style="font-weight: 400">Monitor Query Executions</span><a class="anchor-link" id="monitor-query-executions"></a></h3>
<p><span style="font-weight: 400">These commands assist in measuring query performance and repeatedly executing queries for monitoring purposes.</span></p>
<table border="1">
<tbody>
<tr>
<td><span style="font-weight: 400">timing [on|off]</span></td>
<td><span style="font-weight: 400">Toggle Query execution timing</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">watch seconds</span></td>
<td><span style="font-weight: 400">Re-execute the current query at the specified interval</span></td>
</tr>
</tbody>
</table>
<p><img loading="lazy" decoding="async" class="alignnone size-medium wp-image-50264" src="https://www.percona.com/wp-content/uploads/2026/07/Screenshot-2026-07-15-at-11.52.04-AM-300x125.png" alt="" width="300" height="125"></p>
<h3><span style="font-weight: 400">Execute and Automate tasks</span><a class="anchor-link" id="execute-and-automate-tasks"></a></h3>
<p><span style="font-weight: 400">These commands simplify repetitive tasks and enable integration between</span><span style="font-weight: 400"> psql,</span><span style="font-weight: 400"> SQL scripts, and the operating system</span></p>
<table border="1">
<tbody>
<tr>
<td><span style="font-weight: 400">i filename</span></td>
<td><span style="font-weight: 400">Execute the commands from the file</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">gexec</span></td>
<td><span style="font-weight: 400">Execute each field returned by a query as an SQL statement.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">! command</span></td>
<td><span style="font-weight: 400">Execute a shell command without leaving a </span><span style="font-weight: 400">psql</span><span style="font-weight: 400"> prompt</span></td>
</tr>
</tbody>
</table>
<p><img decoding="async" loading="lazy" class="alignnone size-medium wp-image-50265" src="https://www.percona.com/wp-content/uploads/2026/07/Screenshot-2026-07-15-at-11.53.25-AM-300x75.png" alt="" width="300" height="75"></p>
<h3><span style="font-weight: 400">Get Help</span><a class="anchor-link" id="get-help"></a></h3>
<p><span style="font-weight: 400">Built-in help commands provide quick access to both </span><span style="font-weight: 400">psql</span><span style="font-weight: 400"> meta-command documentation and PostgreSQL SQL syntax without leaving the terminal.</span></p>
<table border="1">
<tbody>
<tr>
<td><span style="font-weight: 400">?</span></td>
<td><span style="font-weight: 400">Display all available </span><span style="font-weight: 400">psql</span><span style="font-weight: 400"> meta-commands.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">h</span></td>
<td><span style="font-weight: 400">List SQL commands for which syntax help is available.</span></td>
</tr>
<tr>
<td><span style="font-weight: 400">h command</span></td>
<td><span style="font-weight: 400">Display syntax help for a specific SQL command.</span></td>
</tr>
</tbody>
</table>
<h3><a class="anchor-link" id=""></a></h3>
<h3><span style="font-weight: 400">What is .psqlrc?</span><a class="anchor-link" id="what-is-psqlrc"></a></h3>
<p><span style="font-weight: 400">.psqlrc</span><span style="font-weight: 400"> is a startup file in the home directory that </span><span style="font-weight: 400">psql</span><span style="font-weight: 400"> reads when a session begins. It can hold meta-commands and SQL that run before the first prompt. The main benefit is consistent defaults &mdash; timing, formatting, and a custom prompt &mdash; without repeating setup each time, which speeds daily work and reduces connection mistakes across databases.</span></p>
<p><span style="font-weight: 400">A minimal</span><span style="font-weight: 400"> .psqlrc</span><span style="font-weight: 400"> might look like this:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">timing on 
x auto</pre>
<p><span style="font-weight: 400">These settings load automatically on every new</span> <span style="font-weight: 400">psql</span><span style="font-weight: 400"> session as highlighted below:</span></p>
<p><img decoding="async" loading="lazy" class="alignnone size-medium wp-image-50266" src="https://www.percona.com/wp-content/uploads/2026/07/Screenshot-2026-07-15-at-11.54.34-AM-300x77.png" alt="" width="300" height="77"></p>
<h3><span style="font-weight: 400">Conclusion</span><a class="anchor-link" id="conclusion"></a></h3>
<p><span style="font-weight: 400">PostgreSQL is powerful because of SQL &mdash; but for DBAs, </span><span style="font-weight: 400">psql</span><span style="font-weight: 400"> meta commands make daily management far easier and more efficient.</span></p>
<p><span style="font-weight: 400">Most developers use only a handful like </span><span style="font-weight: 400">dt</span><span style="font-weight: 400"> or </span><span style="font-weight: 400">d</span><span style="font-weight: 400">. But experienced DBAs rely on a much broader toolkit to:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Investigate production issues faster</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Navigate systems efficiently</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Reduce reliance on repetitive SQL</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Repetitive tasks can be automated</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Debug complex problems quickly</span></li>
</ul>
<p><span style="font-weight: 400">An easy way to understand the relationship between SQL and PostgreSQL meta commands is to compare them to driving a car.</span></p>
<p><b>SQL is like driving the car</b><span style="font-weight: 400"> &mdash; it is the primary means of reaching a destination. It is used to retrieve, insert, update, and delete data, enabling applications and users to interact with the information stored in the database.</span></p>
<p><b>Meta commands, on the other hand, are like the car&rsquo;s dashboard.</b><span style="font-weight: 400"> While the dashboard does not move the vehicle, it provides essential information such as speed, fuel level, engine health, navigation status, and warning indicators. Driving without a dashboard is certainly possible, but it would mean operating with limited visibility into the vehicle&rsquo;s condition and performance.</span></p>
<p><span style="font-weight: 400">Similarly, SQL is responsible for manipulating and retrieving data, whereas PostgreSQL meta commands provide valuable insight into the database environment itself. They help administrators inspect database objects, navigate schemas, monitor sessions, examine roles and privileges, review object definitions, and perform numerous administrative tasks efficiently.</span></p>
<p><span style="font-weight: 400">In essence, SQL enables interaction with the </span><b>data</b><span style="font-weight: 400">, while meta commands enable interaction with the </span><b>PostgreSQL environment</b><span style="font-weight: 400">. Together, they form a complementary toolkit that allows database professionals to work more effectively, troubleshoot issues faster, and administer PostgreSQL with greater confidence.</span></p>
<p>The post <a href="https://www.percona.com/blog/postgresql-meta-commands-that-save-time-every-day/">PostgreSQL Meta Commands that save time every day</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p><a href="https://www.percona.com/blog/postgresql-meta-commands-that-save-time-every-day/">PostgreSQL Meta Commands that save time every day</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Inside MySQL 9.7 LTS Features</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/inside-mysql-9-7-lts-features/" />
      <id>https://www.percona.com/blog/inside-mysql-9-7-lts-features/</id>
      <updated>2026-07-15T05:00:23+00:00</updated>
      <author><name>Anil Joshi</name></author>
      <summary type="html"><![CDATA[<p>MySQL 9.7, a Long-Term Support (LTS) release, incorporates a variety of potential features spanning across multiple technical domains. This article covers some of the primary features introduced and evaluates their practical utility within the MySQL database environment. Following the End-of-Life (EOL) status of MySQL 8.0, this subsequent LTS release is designed to provide enhanced stability … Continued<br />
The post Inside MySQL 9.7 LTS Features appeared first on Percona.</p>
<p><a href="https://www.percona.com/blog/inside-mysql-9-7-lts-features/">Inside MySQL 9.7 LTS Features</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">MySQL 9.7, a Long-Term Support (LTS) release, incorporates a variety of potential features spanning across multiple technical domains. This article covers some of the primary features introduced and evaluates their practical utility within the MySQL database environment.</span></p>
<p><span style="font-weight: 400">Following the End-of-Life (EOL) status of MySQL 8.0, this subsequent LTS release is designed to provide enhanced stability alongside significant architectural innovations.</span></p>
<p><span style="font-weight: 400">Let&rsquo;s discuss each of these features below with some examples and usage.</span></p>
<h2><span style="font-weight: 400">Flow-control monitoring in Group Replication</span><a class="anchor-link" id="flow-control-monitoring-in-group-replication"></a></h2>
<p><span style="font-weight: 400">Flow control monitoring has been improved and provides more granularity by introducing the additional status variables listed below.</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">Gr_flow_control_throttle_count : It denotes the number of transactions that have been throttled.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Gr_flow_control_throttle_time_sum :It denotes the time in microseconds that transactions have been throttled.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Gr_flow_control_throttle_active_count :It denotes the number of transactions currently being throttled.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Gr_flow_control_throttle_last_throttle_timestamp : It denotes the most recent date and time that a transaction was throttled.</span></li>
</ul>
<p><span style="font-weight: 400">To use these status variables, we must install the &ldquo;</span><b>Group Replication Flow Control Statistics&rdquo;&nbsp; </b><span style="font-weight: 400">component.<br>
</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; Install component 'file://component_group_replication_flow_control_stats';</pre>
<p><span style="font-weight: 400">After the component is installed, the statistics will be visible.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; SELECT * FROM performance_schema.global_status WHERE VARIABLE_NAME LIKE 'Gr_flow_control%';
+--------------------------------------------------+----------------+
| VARIABLE_NAME                                    | VARIABLE_VALUE |
+--------------------------------------------------+----------------+
| Gr_flow_control_throttle_active_count            | 0              |
| Gr_flow_control_throttle_count                   | 0              |
| Gr_flow_control_throttle_last_throttle_timestamp |                |
| Gr_flow_control_throttle_time_sum                | 0              |
+--------------------------------------------------+----------------+</pre>

<h2><span style="font-weight: 400">Multi-threaded applier extended statistics</span><a class="anchor-link" id="multi-threaded-applier-extended-statistics"></a></h2>
<p><span style="font-weight: 400">We now have additional verbosity for the Applier threads for both Asynchronous and Group Replication topologies. This means we can get more details of the transactions or potential misbehaviours during the transactions applier stage. This feature is particularly useful for troubleshooting performance bottlenecks in multi-threaded replication environments, where understanding the specific cause of lag can be challenging.</span></p>
<p><span style="font-weight: 400">This requires installing the &ldquo;</span><b>Replication Applier Metrics&rdquo; </b><span style="font-weight: 400">component.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; Install component 'file://component_replication_applier_metrics';</pre>
<p><span style="font-weight: 400">Upon successful installation of the requisite component, the performance schema tables facilitate tracking of transaction details and various performance metrics during the replication applier phase. For instance, monitoring the table &ldquo;</span><b>replication_applier_metrics&rdquo;</b><span style="font-weight: 400"> enables observing channel-specific operations.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; SELECT * FROM performance_schema.replication_applier_metrics where CHANNEL_NAME='group_replication_applier'G;
*************************** 1. row ***************************
                                CHANNEL_NAME: group_replication_applier
                  TOTAL_ACTIVE_TIME_DURATION: 0
                          LAST_APPLIER_START: 0000-00-00 00:00:00
                TRANSACTIONS_COMMITTED_COUNT: 0
                  TRANSACTIONS_ONGOING_COUNT: 0
                  TRANSACTIONS_PENDING_COUNT: 0
       TRANSACTIONS_COMMITTED_SIZE_BYTES_SUM: 0
    TRANSACTIONS_ONGOING_FULL_SIZE_BYTES_SUM: 0
TRANSACTIONS_ONGOING_PROGRESS_SIZE_BYTES_SUM: 0
         TRANSACTIONS_PENDING_SIZE_BYTES_SUM: NULL
                      EVENTS_COMMITTED_COUNT: 0
            WAITS_FOR_WORK_FROM_SOURCE_COUNT: 0
         WAITS_FOR_WORK_FROM_SOURCE_SUM_TIME: 0
            WAITS_FOR_AVAILABLE_WORKER_COUNT: 0
         WAITS_FOR_AVAILABLE_WORKER_SUM_TIME: 0
      WAITS_COMMIT_SCHEDULE_DEPENDENCY_COUNT: 0
   WAITS_COMMIT_SCHEDULE_DEPENDENCY_SUM_TIME: 0
         WAITS_FOR_WORKER_QUEUE_MEMORY_COUNT: 0
      WAITS_FOR_WORKER_QUEUE_MEMORY_SUM_TIME: 0
              WAITS_WORKER_QUEUES_FULL_COUNT: 0
           WAITS_WORKER_QUEUES_FULL_SUM_TIME: 0
             WAITS_DUE_TO_COMMIT_ORDER_COUNT: 0
          WAITS_DUE_TO_COMMIT_ORDER_SUM_TIME: 0
        TIME_TO_READ_FROM_RELAY_LOG_SUM_TIME: 0</pre>
<p><span style="font-weight: 400">In addition to aggregate metrics, MySQL 9.7 provides a way to inspect the progress of individual worker threads via monitoring stats in the </span><b>&ldquo;replication_applier_progress_by_worker&rdquo;</b><span style="font-weight: 400"> table. This level of detail helps administrators identify if a single transaction is monopolising a specific worker, causing overall replication delay.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; SELECT * FROM performance_schema.replication_applier_progress_by_workerG;
*************************** 1. row ***************************
                          CHANNEL_NAME: group_replication_applier
                             WORKER_ID: 0
                             THREAD_ID: 62
              ONGOING_TRANSACTION_TYPE: UNASSIGNED
   ONGOING_TRANSACTION_FULL_SIZE_BYTES: 0
ONGOING_TRANSACTION_APPLIED_SIZE_BYTES: 0
*************************** 2. row ***************************
                          CHANNEL_NAME: group_replication_applier
                             WORKER_ID: 1
                             THREAD_ID: 63
              ONGOING_TRANSACTION_TYPE: UNASSIGNED
   ONGOING_TRANSACTION_FULL_SIZE_BYTES: 0
ONGOING_TRANSACTION_APPLIED_SIZE_BYTES: 0
*************************** 3. row ***************************
                          CHANNEL_NAME: group_replication_applier
                             WORKER_ID: 2
                             THREAD_ID: 64
              ONGOING_TRANSACTION_TYPE: UNASSIGNED
   ONGOING_TRANSACTION_FULL_SIZE_BYTES: 0
ONGOING_TRANSACTION_APPLIED_SIZE_BYTES: 0
*************************** 4. row ***************************
                          CHANNEL_NAME: group_replication_applier
                             WORKER_ID: 3
                             THREAD_ID: 65
              ONGOING_TRANSACTION_TYPE: UNASSIGNED
   ONGOING_TRANSACTION_FULL_SIZE_BYTES: 0
ONGOING_TRANSACTION_APPLIED_SIZE_BYTES: 0</pre>

<h2><span style="font-weight: 400">Automatic eviction &amp; rejoin</span><a class="anchor-link" id="automatic-eviction-rejoin"></a></h2>
<p><span style="font-weight: 400">The Group Replication resource manager now provides auto-eviction functionality, which we can configure using the available options. This basically ensures that the unhealthy node is removed from the Group to maintain the cluster&rsquo;s high availability and overall performance.</span></p>
<p><span style="font-weight: 400">This requires installing the &ldquo;</span><b>group replication resource manager&rdquo;</b><span style="font-weight: 400"> component.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; INSTALL COMPONENT 'file://component_group_replication_resource_manager';</pre>
<p><span style="font-weight: 400">Once the component is available,&nbsp; we can use various options to decide the node expulsion policy.</span></p>
<p><b>1) Applier channel</b></p>
<p><span style="font-weight: 400">We can set the applier channel replication lag threshold values using the configuration parameter below.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; set global group_replication_resource_manager.applier_channel_lag = &lt;value&gt;;</pre>
<p><span style="font-weight: 400">If lag exceeds&nbsp; &ldquo;</span><b>applier_channel_lag&rdquo;</b><span style="font-weight: 400">&nbsp;threshold 10 times or more in a row, this server is expelled from the group. The status variable below is used for tracking the lag exceed rate.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; show global status like 'Gr_resource_manager_applier_channel_lag';
+-----------------------------------------+-------+
| Variable_name                           | Value |
+-----------------------------------------+-------+
| Gr_resource_manager_applier_channel_lag | 0     |
+-----------------------------------------+-------+</pre>
<p><span style="font-weight: 400"><br>
</span><b>2)</b> <b>Recovery Channel</b><b></b></p>
<p><span style="font-weight: 400">Similarly, we can define a threshold for the group member recovery process to attempt to rejoin the cluster.&nbsp;</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; set global group_replication_resource_manager.recovery_channel_lag = &lt;value&gt;;</pre>
<p><span style="font-weight: 400">If the secondary&rsquo;s recovery lag exceeds &ldquo;</span><strong>recovery_channel_lag&rdquo;</strong><span style="font-weight: 400">, 10 times or more in succession, the server is expelled from the group.&nbsp;</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql show global status like 'Gr_resource_manager_recovery_channel_lag';
+------------------------------------------+-------+
| Variable_name                            | Value |
+------------------------------------------+-------+
| Gr_resource_manager_recovery_channel_lag | 0     |
+------------------------------------------+-------+</pre>
<p><b>3) Memory/Resource Usage</b></p>
<p><span style="font-weight: 400">We can also define an expelled condition based on the group member&rsquo;s memory or resource usage %.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; set global group_replication_resource_manager.memory_used_limit = 10;</pre>
<p><span style="font-weight: 400">If the memory usage exceeds </span><strong>memory_used_limit</strong><span style="font-weight: 400">&nbsp;% by 10 or more consecutive times, the node will be expelled from the group.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; show global status like 'Gr_resource_manager_memory_used%';
+---------------------------------+-------+
| Variable_name                   | Value |
+---------------------------------+-------+
| Gr_resource_manager_memory_used | 78    |
+---------------------------------+-------+
1 row in set (0.002 sec)</pre>
<p><span style="font-weight: 400">In addition to the discussed options above, we can also track various <a href="https://dev.mysql.com/doc/refman/9.7/en/group-replication-resource-manager-component.html">server status variables</a> to monitor group replication and the resource manager component.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; select * from performance_schema.global_status where variable_name in ('Gr_resource_manager_applier_channel_threshold_hits','Gr_resource_manager_applier_channel_eviction_timestamp','Gr_resource_manager_recovery_channel_threshold_hits','Gr_resource_manager_recovery_channel_eviction_timestamp','Gr_resource_manager_memory_threshold_hits','Gr_resource_manager_memory_eviction_timestamp');
+---------------------------------------------------------+----------------+
| VARIABLE_NAME                                           | VARIABLE_VALUE |
+---------------------------------------------------------+----------------+
| Gr_resource_manager_applier_channel_eviction_timestamp  |                |
| Gr_resource_manager_applier_channel_threshold_hits      | 0              |
| Gr_resource_manager_memory_eviction_timestamp           |                |
| Gr_resource_manager_memory_threshold_hits               | 6703           |
| Gr_resource_manager_recovery_channel_eviction_timestamp |                |
| Gr_resource_manager_recovery_channel_threshold_hits     | 0              |
+---------------------------------------------------------+----------------+
6 rows in set (0.003 sec)</pre>
<p><span style="font-weight: 400">The expelled node can attempt to automatically rejoin based on the value of the </span><b>group_replication_autorejoin_tries</b><span style="font-weight: 400"> variable</span><b>.</b></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; show variables like '%group_replication_autorejoin_tries%';
+------------------------------------+-------+
| Variable_name                      | Value |
+------------------------------------+-------+
| group_replication_autorejoin_tries | 3     |
+------------------------------------+-------+
1 row in set (0.006 sec)</pre>
<p><span style="font-weight: 400">If the node cannot join, it will perform the behaviour specified in the </span><b>group_replication_exit_state_action </b><span style="font-weight: 400">variable.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; show variables like '%group_replication_exit_state_action%';
+-------------------------------------+--------------+
| Variable_name                       | Value        |
+-------------------------------------+--------------+
| group_replication_exit_state_action | OFFLINE_MODE |
+-------------------------------------+--------------+
1 row in set (0.005 sec)</pre>
<p><span style="font-weight: 400">After a server is evicted from the group (for whatever reason), it gets a </span><b>grace period</b><span style="font-weight: 400"> (</span><b>group_replication_resource_manager</b><span style="font-weight: 400">) when it rejoins. During this period, the Resource Manager won&rsquo;t immediately kick it out again, even if it&rsquo;s still lagging or breaching the defined threshold as discussed above.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; show variables like '%group_replication_resource_manager.quarantine_time%';
+----------------------------------------------------+-------+
| Variable_name                                      | Value |
+----------------------------------------------------+-------+
| group_replication_resource_manager.quarantine_time | 3600  |
+----------------------------------------------------+-------+</pre>

<h2><span style="font-weight: 400">Up-to-date aware Primary election</span><a class="anchor-link" id="up-to-date-aware-primary-election"></a></h2>
<p><span style="font-weight: 400">The Primary election process is more mature and cohesive. The Group Replication Manager now uses the most up-to-date status as a criterion for selecting the new primary.</span></p>
<p><span style="font-weight: 400">Here is how the Group Replication Manager performs the most up-to-date primary selection prior to MySQL v9.7.</span></p>
<ol>
<li style="font-weight: 400"><span style="font-weight: 400">The lowest MySQL version is checked for each member.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">If more than one member is running the lowest MySQL Server version, each member&rsquo;s weight is determined by the &ldquo;</span>group_replication_member_weight&rdquo;<span style="font-weight: 400">&nbsp;system variable.</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">If there is more than one member running the lowest MySQL Server version, and also more than one of those members has the highest member weight, the third factor considered is the lexicographical order of the generated server UUIDs &ldquo;</span>server_uuid&rdquo;<span style="font-weight: 400">&nbsp;of each group member. The member with the lowest server UUID is chosen as the new primary.<br>
</span></li>
</ol>
<p><span style="font-weight: 400">In MySQL version 9.7, &ldquo;</span><b>group_replication_elect_prefers_most_updated&rdquo;</b><span style="font-weight: 400">&nbsp;was introduced, so the failover will be determined by </span><b>how many transactions are in the secondary backlog</b><span style="font-weight: 400">. Basically the secondary with the least backlog will be selected as Primary.</span></p>
<p><span style="font-weight: 400">Now, it will consider the</span><b> &ldquo;most up-to-date&rdquo; </b><span style="font-weight: 400">node first,</span> <span style="font-weight: 400">then &ldquo;</span><b>weight&rdquo;</b><span style="font-weight: 400"> and then &ldquo;</span><b>UUID&rdquo;</b><span style="font-weight: 400">.&nbsp;</span></p>
<p><span style="font-weight: 400">To use &ldquo;</span><b>group_replication_elect_prefers_most_updated&rdquo;</b><span style="font-weight: 400">, we need to install the &ldquo;</span><b>Group Replication Primary Election</b><span style="font-weight: 400">&rdquo; component listed below on each Group Member.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; Install component 'file://component_group_replication_elect_prefers_most_updated';</pre>
<p><span style="font-weight: 400">By default, the most up-to-date group member selection is enabled. We need to make sure it&rsquo;s enabled on all Group Members.&nbsp;</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; select @@group_replication_elect_prefers_most_updated.enabled;
+--------------------------------------------------------+
| @@group_replication_elect_prefers_most_updated.enabled |
+--------------------------------------------------------+
|                                                      1 |
+--------------------------------------------------------+
1 row in set (0.007 sec)</pre>
<p><span style="font-weight: 400">In the event that a new primary is elected via the most up-to-date selection mechanism, this metric represents the transaction processing differential between the newly designated primary and the secondary node with the highest level of synchronisation.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; show status like 'Gr_latest_primary_election_by_most_uptodate_members_trx_delta';
+---------------------------------------------------------------+-------+
| Variable_name                                                 | Value |
+---------------------------------------------------------------+-------+
| Gr_latest_primary_election_by_most_uptodate_members_trx_delta | 0     |
+---------------------------------------------------------------+-------+</pre>
<p><span style="font-weight: 400">Also, we can track the timestamp of the most recent primary election on the most up-to-date node.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; show status like 'Gr_latest_primary_election_by_most_uptodate_member_timestamp';
+--------------------------------------------------------------+-------+
| Variable_name                                                | Value |
+--------------------------------------------------------------+-------+
| Gr_latest_primary_election_by_most_uptodate_member_timestamp |       |
+--------------------------------------------------------------+-------+
1 row in set (0.005 sec)</pre>
<p><span style="font-weight: 400">The database logs also tell exactly what criteria the primary member selected during failover.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">2026-06-14T10:04:02.243809Z 0 [System] [MY-015575] [Repl] Plugin group_replication reported: 'Member with uuid 00021702-2222-2222-2222-222222222222 was elected primary since it was the most up-to-date member with 2755 transactions more than second most up-to-date member 00021703-3333-3333-3333-333333333333. In case of a tie member weight and then uuid lexical order was used over the most updated members.'</pre>

<h2><span style="font-weight: 400">MySQL JSON duality views</span><a class="anchor-link" id="mysql-json-duality-views"></a></h2>
<p><span style="font-weight: 400">With the introduction of JSON duality views, we can leverage a single unified JSON document for both relational and hierarchical JSON data. This provides a common, structured JSON format for the application, allowing it to perform both read and write operations.</span></p>
<p><span style="font-weight: 400">Let&rsquo;s see a quick scenario below on how it works.</span></p>
<p><span style="font-weight: 400">Below are two relational tables from which we obtain aggregated information in JSON format.&nbsp;</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; CREATE TABLE products (
  product_id INT PRIMARY KEY,
  product_type VARCHAR(100)
);

mysql&gt; CREATE TABLE products_details (
  product_detail_id INT PRIMARY KEY,
  product_id INT,
  name VARCHAR(100),
  active varchar(10)
);</pre>

<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; INSERT INTO products (product_id,product_type) VALUES (1,'IT'), (2,'TEL');
mysql&gt; INSERT INTO products_details (product_detail_id,product_id,name,active) VALUES (1,1,'Laptop','Yes'), (2,2,'Mobile','Yes');</pre>
<p><span style="font-weight: 400">Here is the exact Json View which fetch the columns from the relation table based on the join condition. Each of those relational table columns is mapped with a JSON data structure (</span><b>_id</b><span style="font-weight: 400">,</span><b>v_product_type</b><span style="font-weight: 400">,</span><b>v_product_type</b><span style="font-weight: 400"> ), and the complete details of the</span><b> product details </b><span style="font-weight: 400">table are fetched into the (</span><b>product</b><span style="font-weight: 400">) array.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; CREATE JSON RELATIONAL DUALITY VIEW view_product AS
SELECT JSON_DUALITY_OBJECT( WITH(INSERT,UPDATE,DELETE)
    '_id': product_id,
    'v_product_type': product_type,
    'product': (
        SELECT JSON_ARRAYAGG(
            JSON_DUALITY_OBJECT(WITH(INSERT,UPDATE,DELETE)
                'v_product_detail_id': product_detail_id,
                'v_name': name,
                'v_active': active
                
            )
        )
        FROM products_details
        WHERE products_details.product_id = products.product_id
    )
)
FROM products;</pre>

<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; select * from view_product;
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| data                                                                                                                                                                           |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| {"_id": 1, "product": [{"v_name": "Laptop", "v_active": "Yes", "v_product_detail_id": 1}], "_metadata": {"etag": "313642c2aa24f0571264332afa140715"}, "v_product_type": "IT"}  |
| {"_id": 2, "product": [{"v_name": "Mobile", "v_active": "Yes", "v_product_detail_id": 2}], "_metadata": {"etag": "3d229ada02ac660f9f6cac994b44831a"}, "v_product_type": "TEL"} |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.002 sec)</pre>
<p><span style="font-weight: 400">Once the duality view is created, we can perform both read/write operations.</span></p>
<p><strong>Reading the duality view</strong></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; select * from view_product;
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| data                                                                                                                                                                           |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| {"_id": 1, "product": [{"v_name": "Laptop", "v_active": "Yes", "v_product_detail_id": 1}], "_metadata": {"etag": "313642c2aa24f0571264332afa140715"}, "v_product_type": "IT"}  |
| {"_id": 2, "product": [{"v_name": "Mobile", "v_active": "Yes", "v_product_detail_id": 2}], "_metadata": {"etag": "3d229ada02ac660f9f6cac994b44831a"}, "v_product_type": "TEL"} |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+</pre>
<p><strong>Writing the underlying table in the duality view</strong></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; UPDATE view_product
SET data = JSON_SET(
    data,
    '$.product[0].v_name',
    'Notepad'
)
WHERE JSON_EXTRACT(data, '$._id') = 1;</pre>

<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; select * from products_details;
+-------------------+------------+---------+--------+
| product_detail_id | product_id | name    | active |
+-------------------+------------+---------+--------+
|                 1 |          1 | Notepad | Yes    |
|                 2 |          2 | Mobile  | Yes    |
+-------------------+------------+---------+--------+</pre>
<p><span style="font-weight: 400">After performing the above write operations, we can see that the view now shows the updated data.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql &gt; select * from view_product;
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| data                                                                                                                                                                           |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| {"_id": 1, "product": [{"v_name": "Notepad", "v_active": "Yes", "v_product_detail_id": 1}], "_metadata": {"etag": "72c4368420cdc698842d0ab4bd9315ab"}, "v_product_type": "IT"} |
| {"_id": 2, "product": [{"v_name": "Mobile", "v_active": "Yes", "v_product_detail_id": 2}], "_metadata": {"etag": "3d229ada02ac660f9f6cac994b44831a"}, "v_product_type": "TEL"} |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+</pre>

<h2><span style="font-weight: 400">Hypergraph Optimizer</span><a class="anchor-link" id="hypergraph-optimizer"></a></h2>
<p><span style="font-weight: 400">With the Hypergraph Optimiser, we now have more </span><span style="font-weight: 400">advanced optimisation for complex queries and a broader set of Join plans than the older traditional method, missing earlier. By using &ldquo;</span><b>Join hypergraph</b><span style="font-weight: 400">&rdquo;, the optimiser now has better reach to all tables in the join condition.</span></p>
<p><b>Hypergraph Optimiser is OFF</b></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; SET optimizer_switch='hypergraph_optimizer=off';</pre>

<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; SELECT t1.k, COUNT(*) AS cnt
FROM sbtest1 t1
JOIN sbtest2 t2 ON t1.id = t2.id
JOIN sbtest3 t3 ON t1.id = t3.id
WHERE t1.k BETWEEN 200000 AND 500000
GROUP BY t1.k
ORDER BY cnt DESC
LIMIT 100;</pre>
<p><span style="font-weight: 400"><strong>Output</strong>:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">| 498870 | 119 |
| 498729 | 119 |
| 497668 | 119 |
| 498076 | 119 |
+--------+-----+
100 rows in set (4.000 sec)</pre>
<p><strong>Explain output:</strong></p>
<pre class="urvanov-syntax-highlighter-plain-tag">-&gt; Limit: 100 row(s)
    -&gt; Sort: cnt DESC, limit input to 100 row(s) per chunk
        -&gt; Stream results  (cost=1.22e+6 rows=175136)
            -&gt; Group aggregate: count(0)  (cost=1.22e+6 rows=175136)
                -&gt; Nested loop inner join  (cost=1.1e+6 rows=493200)
                    -&gt; Nested loop inner join  (cost=601547 rows=493200)
                        -&gt; Filter: (t1.k between 200000 and 500000)  (cost=99122 rows=493200)
                            -&gt; Covering index range scan on t1 using k_1 over (200000 &lt;= k &lt;= 500000)  (cost=99122 rows=493200)
                        -&gt; Single-row covering index lookup on t2 using PRIMARY (id = t1.id)  (cost=0.919 rows=1)
                    -&gt; Single-row covering index lookup on t3 using PRIMARY (id = t1.id)  (cost=0.919 rows=1)</pre>
<p><b>Hypergraph Optimiser is ON</b></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; SET optimizer_switch='hypergraph_optimizer=on';</pre>

<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; SELECT t1.k, COUNT(*) AS cnt
FROM sbtest1 t1
JOIN sbtest2 t2 ON t1.id = t2.id
JOIN sbtest3 t3 ON t1.id = t3.id
WHERE t1.k BETWEEN 200000 AND 500000
GROUP BY t1.k
ORDER BY cnt DESC
LIMIT 100;</pre>
<p><b>Output:</b></p>
<pre class="urvanov-syntax-highlighter-plain-tag">| 499721 | 119 |
| 499052 | 119 |
| 498870 | 119 |
| 498384 | 119 |
+--------+-----+
100 rows in set (0.498 sec)</pre>
<p><strong>Explain output:</strong></p>
<pre class="urvanov-syntax-highlighter-plain-tag">-&gt; Sort: cnt DESC, limit input to 100 row(s) per chunk  (cost=1.96e+6..1.96e+6 rows=100)
    -&gt; Table scan on &lt;temporary&gt;  (cost=1.87e+6..1.9e+6 rows=175136)
        -&gt; Aggregate using temporary table  (cost=1.87e+6..1.87e+6 rows=175136)
            -&gt; Inner hash join (t2.id = t3.id)  (cost=990754..1.44e+6 rows=493200)
                -&gt; Covering index scan on t3 using k_1  (cost=0.312..308240 rows=986400)
                -&gt; Hash
                    -&gt; Inner hash join (t1.id = t2.id)  (cost=370988..824021 rows=493200)
                        -&gt; Covering index scan on t2 using k_1  (cost=0.312..308240 rows=986400)
                        -&gt; Hash
                            -&gt; Filter: (t1.k between 200000 and 500000)  (cost=0.416..205287 rows=493200)
                                -&gt; Covering index range scan on t1 using k_1 over (200000 &lt;= k &lt;= 500000)  (cost=0.359..176877 rows=493200)</pre>
<p><span style="font-weight: 400">We can see that with &ldquo;</span><b>hypergraph_optimizer=enabled&rdquo;, </b><span style="font-weight: 400">the query execution time is almost 8x faster.</span></p>
<p><span style="font-weight: 400">The performance difference might not be noticeable with a few joins or a smaller table&rsquo;s data set, but with more complex joins, it can yield better performance. In the above example, we can see that when</span><b> &ldquo;hypergraph_optimizer=enabled&rdquo;</b><span style="font-weight: 400">, the optimiser replaces &ldquo;</span><b>Nested loop inner join</b><span style="font-weight: 400">&rdquo; with &ldquo;</span><b>Inner</b> <b>hash join</b><span style="font-weight: 400">&rdquo;, which is generally better for large datasets.&nbsp;</span></p>
<h2><span style="font-weight: 400">Higher version source allowed</span><a class="anchor-link" id="higher-version-source-allowed"></a></h2>
<p><span style="font-weight: 400">Now, it&rsquo;s possible that a lower version replica can connect to a higher version source when the major versions differ. That means we don&rsquo;t have to rely on all replicas being upgraded in one go; we can just upgrade the source, verify it, and later perform rolling upgrades on lower-version replicas as per our own timelines and convenience.</span></p>
<p><span style="font-weight: 400">Of course, we have to be cautious not to run any such feature or change on the source that doesn&rsquo;t support lower-version replicas.</span></p>
<p><b>Please note &ndash;</b><span style="font-weight: 400"> This won&rsquo;t be applicable to previous releases, say (8.4, 8.0), as they didn&rsquo;t restrict such replication connectivity. It would be useful for 9.7 or the next major release.</span></p>
<p><span style="font-weight: 400">To enable this functionality, we need to ensure the following variable is enabled on the Replica. By default its enabled on 9.7</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; show variables like 'replica_allow_higher_version_source';
+-------------------------------------+-------+
| Variable_name                       | Value |
+-------------------------------------+-------+
| replica_allow_higher_version_source | ON    |
+-------------------------------------+-------+
1 row in set (0.008 sec)</pre>

<h2><a class="anchor-link" id=""></a></h2>
<h2><span style="font-weight: 400">Summary</span><a class="anchor-link" id="summary"></a></h2>
<p><span style="font-weight: 400">The above discussion highlights key advancements in MySQL 9.7 LTS, ranging from some innovative or operational improvements to developer-centric features such as &ldquo;JSON Duality&rdquo; Views. Also, the &ldquo;Hypergraph Optimiser&rdquo; is now available for community release, which was previously exclusive to MySQL Heatwave/Enterprise.&nbsp; As a Long-Term Support (LTS) release, MySQL 9.7 is structured to provide a stable and consistent environment, prioritising architectural reliability over frequent experimental changes.</span></p>
<p><b>One more important mention here</b><span style="font-weight: 400">: It&rsquo;s suggested to use MySQL 9.7.1, or the next sub-releases, as 9.7.0 has some</span><a href="https://www.oracle.com/security-alerts/cspujun2026verbose.html"><span style="font-weight: 400"> higer severity CVE&rsquo;s</span></a><span style="font-weight: 400">. If you are using </span><b>Percona Server for MySQL (PS), </b><span style="font-weight: 400">we</span> <a href="https://www.percona.com/blog/percona-server-mysql-8-4-9-9-7-0-skipped/"><b>skipped 9.7.0</b></a> <span style="font-weight: 400">and are shipping the fixed 9.7.1 version directly</span><b>.</b></p>
<p><b>Still, it&rsquo;s highly recommended</b><span style="font-weight: 400"> to test any new component or changes in your lower/staging environment before deploying in production to better assess the overall impact on existing workload, queries, and database behaviour.</span></p>
<p><span style="font-weight: 400">&nbsp;</span></p>
<p>The post <a href="https://www.percona.com/blog/inside-mysql-9-7-lts-features/">Inside MySQL 9.7 LTS Features</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p><a href="https://www.percona.com/blog/inside-mysql-9-7-lts-features/">Inside MySQL 9.7 LTS Features</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Hidden Gem: Online Schema Change without pt-osc</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-hidden-gem-online-schema-change-without-pt-osc/" />
      <id>https://mariadb.org/mariadb-hidden-gem-online-schema-change-without-pt-osc/</id>
      <updated>2026-07-14T09:12:43+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>When people hear “online schema change” in the MySQL and MariaDB world, many immediately think about pt-online-schema-change. And for good reasons: for years, changing a large table in production was one of those tasks that could ruin your day. …<br />
Continue reading \"MariaDB Hidden Gem: Online Schema Change without pt-osc\"<br />
The post MariaDB Hidden Gem: Online Schema Change without pt-osc appeared first on MariaDB.org.</p>
<p><a href="https://mariadb.org/mariadb-hidden-gem-online-schema-change-without-pt-osc/">MariaDB Hidden Gem: Online Schema Change without pt-osc</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>When people hear &ldquo;online schema change&rdquo; in the MySQL and MariaDB world, many immediately think about pt-online-schema-change. And for good reasons: for years, changing a large table in production was one of those tasks that could ruin your day. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-hidden-gem-online-schema-change-without-pt-osc/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Hidden Gem: Online Schema Change without pt-osc&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-hidden-gem-online-schema-change-without-pt-osc/">MariaDB Hidden Gem: Online Schema Change without pt-osc</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p><a href="https://mariadb.org/mariadb-hidden-gem-online-schema-change-without-pt-osc/">MariaDB Hidden Gem: Online Schema Change without pt-osc</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MyDumper Locking Mechanisms Revisited: Introducing SAFE_NO_LOCK</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/mydumper-locking-mechanisms-revisited-introducing-safe_no_lock/" />
      <id>https://www.percona.com/blog/mydumper-locking-mechanisms-revisited-introducing-safe_no_lock/</id>
      <updated>2026-07-13T12:24:41+00:00</updated>
      <author><name>David Ducos</name></author>
      <summary type="html"><![CDATA[<p>About a year ago, we discussed how MyDumper refactored its locking mechanisms to move away from old, rigid flags and transitioned towards more flexible, streamlined execution. Since then, the MyDumper community hasn’t stood still. In recent releases, the locking architecture was further standardized under a single overarching option: --sync-thread-lock-mode. Along with this modernization came a … Continued<br />
The post MyDumper Locking Mechanisms Revisited: Introducing SAFE_NO_LOCK appeared first on Percona.</p>
<p><a href="https://www.percona.com/blog/mydumper-locking-mechanisms-revisited-introducing-safe_no_lock/">MyDumper Locking Mechanisms Revisited: Introducing SAFE_NO_LOCK</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>About a year ago, we discussed how <a href="https://www.percona.com/blog/mydumper-refactors-locking-mechanisms/">MyDumper refactored its locking mechanisms</a> to move away from old, rigid flags and transitioned towards more flexible, streamlined execution. Since then, the MyDumper community hasn&rsquo;t stood still.</p>
<p>In recent releases, the locking architecture was further standardized under a single overarching option: <code>--sync-thread-lock-mode</code>. Along with this modernization came a powerful new safety feature designed to give you lock-free thread synchronization without risking silent inconsistency: SAFE_NO_LOCK (merged in PR <a href="https://github.com/mydumper/mydumper/pull/2031">#2031</a>).</p>
<p>Let&rsquo;s explore the new thread-synchronization landscape and break down when you should use each mode.</p>
<h2>What is <code>--sync-thread-lock-mode</code>?<a class="anchor-link" id="what-is-sync-thread-lock-mode"></a></h2>
<p>Previously, flags like <code>-k</code>, <code>--no-locks</code> or <code>--lock-all-tables</code> dictated how MyDumper behaved. These have now been deprecated in favor of <code>--sync-thread-lock-mode</code>, which accepts five core values: AUTO, FTWRL, LOCK_ALL, GTID, NO_LOCK, and the newly added SAFE_NO_LOCK.</p>
<p>As a multi-threaded tool, MyDumper&rsquo;s main challenge is ensuring that every single worker thread establishes its database snapshot at the exact same point in time. The sync mode you choose completely alters how MyDumper orchestrates this point-in-time synchronization.</p>
<h2>Understanding SAFE_NO_LOCK<a class="anchor-link" id="understanding-safe_no_lock"></a></h2>
<p>MyDumper fires off START TRANSACTION WITH CONSISTENT SNAPSHOT across its threads. It captures the binary log position at the very beginning of the process and compares it after the worker threads have attempted to synchronize.</p>
<p><span style="font-weight: 400">When using NO_LOCK, if the threads don&rsquo;t actually hit the same point in time&mdash;meaning they fail to synchronize&mdash;MyDumper simply logs a warning and continues backing up. This results in an inconsistent backup, which is a massive gamble for production systems.</span></p>
<p>SAFE_NO_LOCK adds a strict transactional safety net. If MyDumper detects any differences or drift in the binlog position among the threads during the synchronization phase, it immediately stops the backup. This prevents you from generating a corrupted, out-of-sync backup that will fail or cause data anomalies during a later restore.</p>
<h2>Choosing the Right Mode<a class="anchor-link" id="choosing-the-right-mode"></a></h2>
<p>Depending on your architecture, uptime requirements, and database vendor, here is the breakdown of when to use each mode:</p>
<h3>AUTO (The Default)<a class="anchor-link" id="auto-the-default"></a></h3>
<p>What it does: MyDumper <strong>automatically evaluates</strong> the database vendor, version, and capabilities to choose the safest, <strong>least-intrusive method</strong>.</p>
<p>When to use it: The vast majority of standard backups. It <strong>removes the guesswork</strong> and adapts dynamically if your database infrastructure upgrades.</p>
<h3>FTWRL (Flush Tables With Read Lock)<a class="anchor-link" id="ftwrl-flush-tables-with-read-lock"></a></h3>
<p>What it does: It is the traditional method. It issues a <strong>global read lock</strong> via FLUSH TABLES WITH READ LOCK on the main connection, forces all threads to establish their consistent snapshot at that exact freeze frame, and then releases the lock.</p>
<p>When to use it:</p>
<ul>
<li>When you have non-transactional tables (like MyISAM or ARCHIVE) that must be consistently backed up alongside InnoDB tables.</li>
<li>When your database lacks advanced snapshot-tracking capabilities (older MySQL versions).</li>
</ul>
<p><span style="font-weight: 400">Downside: It </span><b>blocks writes</b><span style="font-weight: 400"> across the entire instance during synchronization, which can cause a queue cascade </span><b>on a busy production server</b><span style="font-weight: 400">.</span></p>
<h3>GTID<a class="anchor-link" id="gtid"></a></h3>
<p><span style="font-weight: 400">Leverages a specific server variable in Percona Server called binlog_snapshot_gtid_executed to instantly verify if all threads are watching the exact same transaction state.</span></p>
<p>When to use it: If you are running <strong>Percona Server with GTID enabled</strong> and want a lightning-fast, lockless synchronization method that is guaranteed to be transactionally accurate.</p>
<h3>SAFE_NO_LOCK<a class="anchor-link" id="safe_no_lock"></a></h3>
<p>What it does: Uses transaction isolation to sync threads without global locks, but immediately aborts the backup if binlog positions diverge during initialization.</p>
<p>When to use it:</p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">On highly sensitive production systems, where </span><b>global write locks are absolutely forbidden</b><span style="font-weight: 400"> due to strict SLAs.</span></li>
<li>When you are <strong>entirely utilizing transactional engines</strong> (InnoDB).</li>
<li>When you want a <strong>lock-free backup</strong> but require absolute certainty that your backup is <strong>100% consistent</strong>.</li>
</ul>
<p><span style="font-weight: 400">Downside: In high-throughput write environments, threads may fail to align within the retry window, causing the backup job to abort. (Though an abort is always preferable to an inconsistent backup!).</span></p>
<h3>NO_LOCK<a class="anchor-link" id="no_lock"></a></h3>
<p>What it does: Attempts lockless synchronization but logs a warning and proceeds even if consistency fails.</p>
<p><span style="font-weight: 400">When to use it: Rarely, if ever, in the production primary server. It is </span><b>acceptable for staging environments</b><span style="font-weight: 400">, development seeding, or scratch pads where data accuracy and point-in-time consistency are entirely secondary to getting a quick data dump without locking the server.</span></p>
<h3>LOCK_ALL<a class="anchor-link" id="lock_all"></a></h3>
<p>What it does: Explicitly issues a <strong>LOCK TABLE</strong> command for every single table being exported.</p>
<p>When to use it: Primarily a fallback mode. Use this only when FLUSH TABLES WITH READ LOCK is completely unavailable due to restricted cloud permissions (certain restricted PaaS environments) or specific database limitations.</p>
<h2>Conclusion<a class="anchor-link" id="conclusion"></a></h2>
<p>The addition of <code>--sync-thread-lock-mode=SAFE_NO_LOCK</code> bridges a long-standing gap in logical MySQL backups: achieving a completely lockless synchronization state without flying blind. By implementing a strict fail-fast policy, MyDumper ensures that database administrators never have to sacrifice backup integrity for system availability.</p>
<p>The post <a href="https://www.percona.com/blog/mydumper-locking-mechanisms-revisited-introducing-safe_no_lock/">MyDumper Locking Mechanisms Revisited: Introducing SAFE_NO_LOCK</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p><a href="https://www.percona.com/blog/mydumper-locking-mechanisms-revisited-introducing-safe_no_lock/">MyDumper Locking Mechanisms Revisited: Introducing SAFE_NO_LOCK</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Running DuckDB as a MySQL 9.7 storage engine</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/running-duckdb-as-a-mysql-9-7-storage-engine/" />
      <id>https://www.percona.com/blog/running-duckdb-as-a-mysql-9-7-storage-engine/</id>
      <updated>2026-07-10T13:38:16+00:00</updated>
      <author><name>Evgeniy Patlan</name></author>
      <summary type="html"><![CDATA[<p>ducksdb-mysql-engine is an experimental build of MySQL 9.7 where a table you mark ENGINE=DuckDB answers analytical queries from DuckDB instead of InnoDB. Same server, same connection, no second copy of the data. On TPC-H at scale factor 10, InnoDB times out on 6 of the 22 queries and burns 1317 seconds on the 16 it … Continued<br />
The post Running DuckDB as a MySQL 9.7 storage engine appeared first on Percona.</p>
<p><a href="https://www.percona.com/blog/running-duckdb-as-a-mysql-9-7-storage-engine/">Running DuckDB as a MySQL 9.7 storage engine</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p align="justify"><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">ducksdb-mysql-engine</span></span></span> <span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">is an experimental build of MySQL 9.7 where a table you mark </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">ENGINE=DuckDB</span></span></span> <span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">answers analytical queries from DuckDB instead of InnoDB. Same server, same connection, no second copy of the data. On TPC-H at scale factor 10, InnoDB times out on 6 of the 22 queries and burns 1317 seconds on the 16 it finishes. The DuckDB tables run all 22 in about 15 seconds.</span></span></span></p>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">It&rsquo;s an experiment, not production software. It patches </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">mysqld</span></span></span> <span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">and has rough edges, which we list at the end. Source is on GitHub under GPLv2:</span></span></span><a href="https://github.com/EvgeniyPatlan/ducksdb-mysql-engine"><span style="color: #000000"> &nbsp; &nbsp; </span><span style="color: #1155cc"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><u>https://github.com/EvgeniyPatlan/ducksdb-mysql-engine</u></span></span></span></a><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">.</span></span></span></p>
<h2 class="western" align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: x-large"><b>Why we made it</b></span></span></span><a class="anchor-link" id="why-we-made-it"></a></h2>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">MySQL is great for transactions and slow at analytics. A wide </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">GROUP BY</span></span></span> <span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">over a few hundred million rows, or a six-way join, takes minutes on InnoDB. The usual fix is to copy the data into a column store and keep it in sync, so now you&rsquo;re running two systems and the pipeline between them.</span></span></span></p>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">We wanted the table itself to be the column store, with the heavy queries offloaded for you. Mark it </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">ENGINE=DuckDB</span></span></span><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">, query it the way you always have, and DuckDB does the analytical work.</span></span></span></p>
<h2 class="western" align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: x-large"><b>What it actually is</b></span></span></span><a class="anchor-link" id="what-it-actually-is"></a></h2>
<p align="justify"><a href="https://duckdb.org/"><span style="color: #1155cc"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><u>DuckDB</u></span></span></span></a> <span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">is an in-process columnar query engine, basically SQLite for OLAP. It stores data by column and it&rsquo;s built for scans and aggregations, which is exactly what a row store is bad at.</span></span></span></p>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">We&rsquo;re not the first to put it behind a relational table. Alibaba&rsquo;s</span></span></span><a href="https://github.com/alibaba/AliSQL"> <span style="color: #1155cc"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><u>AliSQL</u></span></span></span></a> <span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">has had a built-in DuckDB engine for a while. MariaDB shipped</span></span></span><a href="https://mariadb.org/mariadb-duckdb-a-new-playground-for-analytics-a-first-look-at-the-new-storage-engine/"> <span style="color: #1155cc"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><u>MariaDB DuckDB</u></span></span></span></a> <span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">about a week ago, while we were already building ours. AliSQL got there first; MariaDB and we landed on the same idea independently, around the same time, them on MariaDB and us on stock MySQL 9.7. Their engine is the closest comparison to ours, so it&rsquo;s in the benchmarks below.</span></span></span></p>
<h2 class="western" align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: x-large"><b>How it hooks into MySQL</b></span></span></span><a class="anchor-link" id="how-it-hooks-into-mysql"></a></h2>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">MySQL doesn&rsquo;t have a </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">select_handler</span></span></span><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">, the API MariaDB uses to grab a whole </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">SELECT</span></span></span> <span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">and run it inside an engine. We added our own: a </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">handlerton::pushdown_select</span></span></span> <span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">hook.</span></span></span></p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-50220 size-full" src="https://www.percona.com/wp-content/uploads/2026/07/architecture.png" alt="" width="761" height="883"></p>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>The pushdown path. Either the whole query renders to DuckDB SQL and runs columnar, or it declines and the normal row path handles it.</i></span></span></span></p>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">The engine is compiled into </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">mysqld</span></span></span><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">, and each schema is one DuckDB file under the datadir. Three patches do the integration, and all three are generic, so they&rsquo;ll fire for any engine that exposes the hook:</span></span></span></p>
<ul>
<li><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">The hook runs at the end of <span style="font-family: Times New Roman, serif">JOIN::optimize()</span>. If every base table in the block is one engine that has the hook, that engine looks at the optimized <span style="font-family: Times New Roman, serif">JOIN</span>, and if it can translate the whole query it sets <span style="font-family: Times New Roman, serif">JOIN::override_executor_func</span> (which the executor already checks in <span style="font-family: Times New Roman, serif">sql_union.cc</span>). The query gets regenerated as DuckDB SQL, prepared once, run, and the aggregated result is staged into a temp table. <span style="font-family: Times New Roman, serif">EXPLAIN</span> is left alone.</span></span></span></li>
<li><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">A server-side <span style="font-family: Times New Roman, serif">LOAD DATA INFILE</span> goes into a DuckDB <span style="font-family: Times New Roman, serif">COPY</span> instead of crawling through <span style="font-family: Times New Roman, serif">write_row</span> row by row. At 600M rows that&rsquo;s a 20-minute load instead of 80.</span></span></span></li>
<li><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">For single-engine statements we clear <span style="font-family: Times New Roman, serif">OPTIMIZER_SWITCH_SEMIJOIN</span> in <span style="font-family: Times New Roman, serif">prepare</span>, so <span style="font-family: Times New Roman, serif">IN</span>, <span style="font-family: Times New Roman, serif">EXISTS</span>, <span style="font-family: Times New Roman, serif">NOT IN</span> and <span style="font-family: Times New Roman, serif">NOT EXISTS</span> stay as subqueries the builder can render instead of getting rewritten into semijoin nests it can&rsquo;t recognize.</span></span></span></li>
</ul>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">The builder only renders a node when the output is provably identical to what MySQL would return. If it can&rsquo;t, it declines and MySQL runs the query unchanged. Literals are bound as parameters. Collation, NULL ordering and decimal scale are matched on purpose, and an unmapped collation or a </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">REAL</span></span></span> <span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">literal is enough to make it back off. With that in place, all 22 TPC-H queries push down and match InnoDB row for row.</span></span></span></p>
<p>&nbsp;</p>
<h2 class="western"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: x-large"><b>Getting started</b></span></span></span><a class="anchor-link" id="getting-started"></a></h2>
<p><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">The fastest way in is the image:</span></span></span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">docker run -d --name mysql-duckdb -p 3306:3306 
-e MYSQL_ROOT_PASSWORD=secret 
-v mysql-duckdb-data:/var/lib/mysql 
evgeniypatlan/test-images:mysql-9.7-duckdb-v0.2.0</pre>
<p><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">Make a table, put a few rows in, run an aggregate:</span></span></span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">CREATE DATABASE shop; USE shop;
CREATE TABLE sales (id INT PRIMARY KEY, region INT, amount DECIMAL(12,2)) ENGINE=DuckDB;
INSERT INTO sales VALUES (1,1,100),(2,1,200),(3,2,50);</pre>

<pre class="urvanov-syntax-highlighter-plain-tag">SELECT region, SUM(amount) FROM sales GROUP BY region;
-- region | SUM(amount)
-- 1 | 300.00
-- 2 | 50.00</pre>
<p>&nbsp;</p>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">Nothing about that query is special, and that is the point. To check it actually went to DuckDB rather than down the row path, watch the </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">Ducksdb_pushdown_count</span></span></span> <span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">status variable:</span></span></span></p>

<pre class="urvanov-syntax-highlighter-plain-tag">SELECT region, SUM(amount) FROM sales GROUP BY region; -- offloaded
SHOW STATUS LIKE 'Ducksdb_pushdown_count'; -- counter goes +1

SELECT * FROM sales WHERE id = 3; -- point lookup
SHOW STATUS LIKE 'Ducksdb_pushdown_count'; -- counter unchanged</pre>
<p>&nbsp;</p>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">The single-row lookup stays on the row path deliberately. For one row an index seek beats spinning up a DuckDB result, so there is no reason to offload it. OLTP keeps its path, analytics get the column store, and you do not pick by hand.</span></span></span></p>
<p><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">If you would rather build it, you need the MySQL 9.7 tree under </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">vendor/mysql-server/</span></span></span> <span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">and a DuckDB prefix, then:</span></span></span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">ln -s ../../engine vendor/mysql-server/storage/duckdb
scripts/build-server.sh # applies the 3 patches, builds mysqld + clients</pre>
<p>&nbsp;</p>
<h2 class="western"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: x-large"><b>Does it actually go fast?</b></span></span></span><a class="anchor-link" id="does-it-actually-go-fast"></a></h2>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">All 22 TPC-H queries, were executed in a Docker on one laptop (20 cores, 62 GiB RAM), the same data loaded into four engines: InnoDB, our MySQL+DuckDB, MariaDB+DuckDB, and standalone DuckDB as the reference. Warm wall-clock, minimum over a few runs, in seconds.</span></span></span></p>
<h3 class="western"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: medium"><b>SF10, around 60 million lineitem rows</b></span></span></span><a class="anchor-link" id="sf10-around-60-million-lineitem-rows"></a></h3>
<p><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">A handful of rows here; the whole table is in </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">docs/tpch_engine_comparison.md</span></span></span><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">:</span></span></span></p>
<table width="643" cellspacing="0" cellpadding="7">
<tbody>
<tr>
<td width="58"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">Query</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">InnoDB</span></span></span></td>
<td width="118"><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: medium">MySQL+DuckDB</span></span></span></td>
<td width="144"><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: medium">MariaDB +DuckDB</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">Native DuckDB</span></span></span></td>
</tr>
<tr>
<td width="58"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">Q1</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">&gt;180</span></span></span></td>
<td width="118"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">1.77</span></span></span></td>
<td width="144"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">0.84</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">0.77</span></span></span></td>
</tr>
<tr>
<td width="58"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">Q5</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">127.6</span></span></span></td>
<td width="118"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">0.53</span></span></span></td>
<td width="144"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">0.46</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">0.71</span></span></span></td>
</tr>
<tr>
<td width="58"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">Q7</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">145.0</span></span></span></td>
<td width="118"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">0.45</span></span></span></td>
<td width="144"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">0.38</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">0.67</span></span></span></td>
</tr>
<tr>
<td width="58"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">Q9</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">&gt;180</span></span></span></td>
<td width="118"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">1.52</span></span></span></td>
<td width="144"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">2.30</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">1.78</span></span></span></td>
</tr>
<tr>
<td width="58"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">Q18</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">101.7</span></span></span></td>
<td width="118"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">1.35</span></span></span></td>
<td width="144"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">1.39</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">1.31</span></span></span></td>
</tr>
<tr>
<td width="58"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">Q19</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">120.4</span></span></span></td>
<td width="118"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">0.15</span></span></span></td>
<td width="144"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">0.67</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">0.83</span></span></span></td>
</tr>
<tr>
<td width="58"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">All 22&nbsp;</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">Finished 16/22</span></span></span></td>
<td width="118"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">15.1s</span></span></span></td>
<td width="144"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">13.3s</span></span></span></td>
<td width="125"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">16.3</span></span></span></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p align="center"><img loading="lazy" decoding="async" class="aligncenter wp-image-50219 size-full" src="https://www.percona.com/wp-content/uploads/2026/07/sf10.png" alt="" width="2233" height="888"></p>
<p align="center"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>SF10, all 22 queries, log scale (lower is better). Hatched InnoDB bars did not finish inside 180 s. The three DuckDB engines sit in a tight band near the floor.</i></span></span></span></p>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">InnoDB is somewhere between 100 and 340 times slower per query, and on 6 of the 22 it never finished inside the 180-second cap (the correlated subqueries and the heaviest scans). It burned 1317 seconds on just the 16 it did finish. The three DuckDB engines get through all 22 in about 15 seconds, and ours lands right between MariaDB and plain DuckDB. The gap is so big there is not much else to say about it.</span></span></span></p>
<h3 class="western"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: medium"><b>SF100, around 600 million lineitem rows</b></span></span></span><a class="anchor-link" id="sf100-around-600-million-lineitem-rows"></a></h3>
<p><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">At this size InnoDB is out of the running (a copy of the data alone is about 100 GB and queries run for hours), so it is the three DuckDB engines only, run one at a time:</span></span></span></p>
<table width="640" cellspacing="0" cellpadding="7">
<tbody>
<tr>
<td width="142"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">Query</span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: medium">MySQL+DuckDB</span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: medium">MariaDB+DuckDB</span></span></span></td>
<td width="148"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">Native DuckDB</span></span></span></td>
</tr>
<tr>
<td width="142"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>Q1</i></span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>15.25</i></span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>6.50</i></span></span></span></td>
<td width="148"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>5.50</i></span></span></span></td>
</tr>
<tr>
<td width="142"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>Q9</i></span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>20.97</i></span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>115.29</i></span></span></span></td>
<td width="148"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>19.54</i></span></span></span></td>
</tr>
<tr>
<td width="142"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>Q10</i></span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>10.64</i></span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>ERR</i></span></span></span></td>
<td width="148"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>8.14</i></span></span></span></td>
</tr>
<tr>
<td width="142"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>Q13</i></span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>19.75</i></span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>ERR</i></span></span></span></td>
<td width="148"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>13.27</i></span></span></span></td>
</tr>
<tr>
<td width="142"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>Q18</i></span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>14.88</i></span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>29.62</i></span></span></span></td>
<td width="148"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>11.08</i></span></span></span></td>
</tr>
<tr>
<td width="142"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>Q19</i></span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>2.91</i></span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>7.98</i></span></span></span></td>
<td width="148"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>6.61</i></span></span></span></td>
</tr>
<tr>
<td width="142"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>Correct</i></span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>22/22</i></span></span></span></td>
<td width="146"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>20/22</i></span></span></span></td>
<td width="148"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>22/22</i></span></span></span></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-50218 size-full" src="https://www.percona.com/wp-content/uploads/2026/07/sf100.png" alt="" width="2233" height="888"></p>
<p align="center"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><i>SF100, three DuckDB engines, log scale (lower is better). Q15 for our engine is shown at its matched-memory time (~4 s); the capped run measured 1309 s, explained below. MariaDB errored on Q10 and Q13.</i></span></span></span></p>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">At 600 million rows ours is still correct on all 22 and stays close to plain DuckDB. MariaDB&rsquo;s engine drops two queries (Q10, and Q13 on its column-list syntax) and is a lot slower on the big joins &ndash; Q9 took 115 seconds against our 21 and native&rsquo;s 20.</span></span></span></p>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">One honest word on Q15 at SF100, because in the full table it shows an ugly number for our engine. It is not a real loss. We capped DuckDB&rsquo;s memory so it spills to disk instead of getting OOM-killed inside </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">mysqld</span></span></span><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">, and under that cap Q15&rsquo;s CTE spills a lot. Give it the memory MariaDB had and it runs in about 4 seconds, like native. The answer was always right; only the clock was bad.</span></span></span></p>
<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">And one number we did not expect: loading those 600 million rows took about 20 minutes with our engine (the </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">COPY</span></span></span> <span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">shortcut) versus about 80 minutes with MariaDB, which loads row by row on a single core. Roughly four times faster to get the data in.</span></span></span></p>
<h2 class="western"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: x-large"><b>Try it, then tell us</b></span></span></span><a class="anchor-link" id="try-it-then-tell-us"></a></h2>
<p><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">If any of this sounds useful, pull the image and throw your own queries at it:</span></span></span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=secret 
evgeniypatlan/test-images:mysql-9.7-duckdb-v0.2.0</pre>

<p align="justify"><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">The source is on GitHub (GPLv2), patches and benchmark harness included:</span></span></span><a href="https://github.com/EvgeniyPatlan/ducksdb-mysql-engine"> <span style="color: #1155cc"><span style="font-family: Arial, sans-serif"><span style="font-size: small"><u>https://github.com/EvgeniyPatlan/ducksdb-mysql-engine</u></span></span></span></a><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">. The full per-query benchmark and how we measured it live in </span></span></span><span style="color: #000000"><span style="font-family: Times New Roman, serif"><span style="font-size: small">docs/tpch_engine_comparison.md</span></span></span><span style="color: #000000"><span style="font-family: Arial, sans-serif"><span style="font-size: small">.</span></span></span></p>
<p>&nbsp;</p>
<p>The post <a href="https://www.percona.com/blog/running-duckdb-as-a-mysql-9-7-storage-engine/">Running DuckDB as a MySQL 9.7 storage engine</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p><a href="https://www.percona.com/blog/running-duckdb-as-a-mysql-9-7-storage-engine/">Running DuckDB as a MySQL 9.7 storage engine</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB 13.1 Feature in Focus: BLOB, TEXT, JSON and GEOMETRY Support in the HEAP Engine</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-13-1-feature-in-focus-blob-text-json-and-geometry-support-in-the-heap-engine/" />
      <id>https://mariadb.org/mariadb-13-1-feature-in-focus-blob-text-json-and-geometry-support-in-the-heap-engine/</id>
      <updated>2026-07-10T04:29:32+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>Some contributions improve MariaDB Server by adding new capabilities.<br />
Some go further: they start from a concrete production problem with an existing feature, not a bug, but a design limitation, solve it upstream, and leave the whole ecosystem better off. …<br />
Continue reading \"MariaDB 13.1 Feature in Focus: BLOB, TEXT, JSON and GEOMETRY Support in the HEAP Engine\"<br />
The post MariaDB 13.1 Feature in Focus: BLOB, TEXT, JSON and GEOMETRY Support in the HEAP Engine appeared first on MariaDB.org.</p>
<p><a href="https://mariadb.org/mariadb-13-1-feature-in-focus-blob-text-json-and-geometry-support-in-the-heap-engine/">MariaDB 13.1 Feature in Focus: BLOB, TEXT, JSON and GEOMETRY Support in the HEAP Engine</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Some contributions improve MariaDB Server by adding new capabilities.<br>
Some go further: they start from a concrete production problem with an existing feature, not a bug, but a design limitation, solve it upstream, and leave the whole ecosystem better off. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/mariadb-13-1-feature-in-focus-blob-text-json-and-geometry-support-in-the-heap-engine/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB 13.1 Feature in Focus: BLOB, TEXT, JSON and GEOMETRY Support in the HEAP Engine&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-13-1-feature-in-focus-blob-text-json-and-geometry-support-in-the-heap-engine/">MariaDB 13.1 Feature in Focus: BLOB, TEXT, JSON and GEOMETRY Support in the HEAP Engine</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p><a href="https://mariadb.org/mariadb-13-1-feature-in-focus-blob-text-json-and-geometry-support-in-the-heap-engine/">MariaDB 13.1 Feature in Focus: BLOB, TEXT, JSON and GEOMETRY Support in the HEAP Engine</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>An Easy Path from MySQL to MariaDB: Introducing MariaDB Migrator</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/an-easy-path-from-mysql-to-mariadb-introducing-mariadb-migrator/" />
      <id>https://mariadb.com/resources/blog/an-easy-path-from-mysql-to-mariadb-introducing-mariadb-migrator/</id>
      <updated>2026-07-09T18:05:57+00:00</updated>
      <author><name>Manoj Vakeel</name></author>
      <summary type="html"><![CDATA[<p>Migrating from MySQL to MariaDB is not considered to be an overly complex process.  After all, the two databases share […]</p>
<p><a href="https://mariadb.com/resources/blog/an-easy-path-from-mysql-to-mariadb-introducing-mariadb-migrator/">An Easy Path from MySQL to MariaDB: Introducing MariaDB Migrator</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Migrating from MySQL to MariaDB is not considered to be an overly complex process. After all, the two databases share the same DNA, as MariaDB was born as a fork initiated by MySQL. Organizations can often make the switch without the complex code rewrites or architectural overhauls required when migrating to entirely different database systems like, say PostgreSQL. However&hellip;</p>
<p><a href="https://mariadb.com/resources/blog/an-easy-path-from-mysql-to-mariadb-introducing-mariadb-migrator/" rel="nofollow">Source</a></p>

<p><a href="https://mariadb.com/resources/blog/an-easy-path-from-mysql-to-mariadb-introducing-mariadb-migrator/">An Easy Path from MySQL to MariaDB: Introducing MariaDB Migrator</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Using MariaDB Serverless Cloud Deployment for Uneven AI Workloads</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/using-mariadb-serverless-cloud-deployment-for-uneven-ai-workloads/" />
      <id>https://mariadb.com/resources/blog/using-mariadb-serverless-cloud-deployment-for-uneven-ai-workloads/</id>
      <updated>2026-07-08T20:24:19+00:00</updated>
      <author><name>Alejandro Duarte</name></author>
      <summary type="html"><![CDATA[<p>The promise of “serverless” is attractive to AI developers. Serverless is a cloud computing model that abstracts away infrastructure management, […]</p>
<p><a href="https://mariadb.com/resources/blog/using-mariadb-serverless-cloud-deployment-for-uneven-ai-workloads/">Using MariaDB Serverless Cloud Deployment for Uneven AI Workloads</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>The promise of &ldquo;serverless&rdquo; is attractive to AI developers. Serverless is a cloud computing model that abstracts away infrastructure management, allowing developers to focus on building applications that scale automatically based on demand while paying only for the resources they consume. It sounds great, but are developers having success using serverless architecture that truly supports the high&hellip;</p>
<p><a href="https://mariadb.com/resources/blog/using-mariadb-serverless-cloud-deployment-for-uneven-ai-workloads/" rel="nofollow">Source</a></p>

<p><a href="https://mariadb.com/resources/blog/using-mariadb-serverless-cloud-deployment-for-uneven-ai-workloads/">Using MariaDB Serverless Cloud Deployment for Uneven AI Workloads</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>MariaDB Foundation Sea Lion Champions Nominees: Federico Razzoli</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-federico-razzoli/" />
      <id>https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-federico-razzoli/</id>
      <updated>2026-07-08T14:18:05+00:00</updated>
      <author><name>Frédéric Descamps</name></author>
      <summary type="html"><![CDATA[<p>Interview with Federico Razzoli, nominated in the Community Leadership 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: Federico Razzoli\"<br />
The post MariaDB Foundation Sea Lion Champions Nominees: Federico Razzoli appeared first on MariaDB.org.</p>
<p><a href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-federico-razzoli/">MariaDB Foundation Sea Lion Champions Nominees: Federico Razzoli</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Interview with Federico Razzoli, nominated in the Community Leadership 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-federico-razzoli/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;MariaDB Foundation Sea Lion Champions Nominees: Federico Razzoli&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-federico-razzoli/">MariaDB Foundation Sea Lion Champions Nominees: Federico Razzoli</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p><a href="https://mariadb.org/mariadb-foundation-sea-lion-champions-nominees-federico-razzoli/">MariaDB Foundation Sea Lion Champions Nominees: Federico Razzoli</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>pg_tde: our fork is temporary, our commitment to open TDE is not</title>
      <link rel="alternate" type="text/html" href="https://percona.community/blog/2026/07/08/pg_tde-our-fork-is-temporary-our-commitment-to-open-tde-is-not/" />
      <id>https://percona.community/blog/2026/07/08/pg_tde-our-fork-is-temporary-our-commitment-to-open-tde-is-not/</id>
      <updated>2026-07-08T00:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>Recently we noticed a LinkedIn post promoting open_pg_tde, a fork of our pg_tde, claiming to be more open. I looked at the repository, and have to disagree with their claim. In this blog post, I’ll explain why.</p>
<p><a href="https://percona.community/blog/2026/07/08/pg_tde-our-fork-is-temporary-our-commitment-to-open-tde-is-not/">pg_tde: our fork is temporary, our commitment to open TDE is not</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>Recently we noticed a LinkedIn post promoting <a href="https://github.com/commandprompt/open_pg_tde" target="_blank" rel="noopener noreferrer">open_pg_tde</a>, a fork of our <a href="https://github.com/percona/pg_tde" target="_blank" rel="noopener noreferrer">pg_tde</a>, claiming to be more open.<br>
I looked at the repository, and have to disagree with their claim.<br>
In this blog post, I&rsquo;ll explain why.</p>
<p>The short version: open_pg_tde needs the exact same modified PostgreSQL that pg_tde does &ndash; TDE isn&rsquo;t possible without those upstream changes.<br>
The only difference is delivery: they ship those changes as a patch file users have to apply by hand, while we provide a ready-made branch.</p>
<h2 id="upstreaming-tde">Upstreaming TDE!<a class="anchor-link" id="upstreaming-tde"></a></h2>
<p>The main point of open_pg_tde is that it is more open, as it is not gated behind a vendor fork.<br>
A bit later I&rsquo;ll go into details why I think that&rsquo;s incorrect from a technical point of view, but before that, I want to make something clear:<br>
we do not want to keep TDE in our fork, we are actively working on upstreaming it!</p>
<p>The LinkedIn post also linked to a pgedge blog post written at the end of May, <a href="https://www.pgedge.com/blog/why-postgres-lacks-transparent-data-encryption" target="_blank" rel="noopener noreferrer">Why Postgres Lacks Transparent Data Encryption</a>.</p>
<p>While it seems to be optimistic about the future of encryption in the community version, it also missed that we had not <a href="https://2026.pgconf.dev/session/559" target="_blank" rel="noopener noreferrer">one</a>, but <a href="https://2026.pgconf.dev/session/738" target="_blank" rel="noopener noreferrer">two</a> sessions about it at pgconf.dev, the first one organized by Kai Wagner from Percona, and the second one by Ants Aasma from Cybertec.</p>
<p>We had some really good discussions during those sessions, and ended up with lots of &ldquo;homework&rdquo;:<br>
things we wanted to explore and benchmark before continuing the discussion on pgsql-hackers, where the discussion will soon continue.</p>
<p>With this I want to emphasize that Percona is 100% behind making PostgreSQL transparent data encryption open, as part of the community.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/07/pg_tde_upstreaming.png" alt="&nbsp;"></figure>
</p>
<p>Our fork was born out of necessity, not because we wanted to have our own version.<br>
In fact, if you look back into the <a href="https://github.com/percona/pg_tde/commits/main/" target="_blank" rel="noopener noreferrer">git history of pg_tde</a>, or at our <a href="https://www.percona.com/blog/protect-your-postgresql-database-with-pg_tde-safe-and-secure/" target="_blank" rel="noopener noreferrer">earlier blog posts</a>, we first tried to make it work without any upstream patch. Unfortunately, that didn&rsquo;t work out.</p>
<h2 id="why-do-we-have-our-fork">Why do we have our fork?<a class="anchor-link" id="why-do-we-have-our-fork"></a></h2>
<p>The open_pg_tde documentation publishes the following comparison table:</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/07/open_pg_tde_comparison.png" alt="&nbsp;"></figure>
</p>
<p>The table is clearly AI generated, and has some misleading and/or inconsistent elements.<br>
Here is what we think a more honest comparison looks like:</p>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>pg_tde</th>
<th>open_pg_tde</th>
</tr>
</thead>
<tbody>
<tr>
<td>Requires a patched PostgreSQL</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>How the patches are delivered</td>
<td>Ready-made fork/branch</td>
<td>Patch file, applied by hand</td>
</tr>
<tr>
<td>Open source patches, cherry-pickable</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>Actually vendor locked</td>
<td>No</td>
<td>No</td>
</tr>
<tr>
<td>API-version safety check</td>
<td>Yes (<code>PERCONA_API_VERSION</code>)</td>
<td>No, it was removed</td>
</tr>
<tr>
<td>Guards against mismatched-package corruption</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr>
<td>Supports PostgreSQL 16, 17, 18</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>Encrypts temporary files at rest</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>Supports all KMIP- and Vault-compatible key providers</td>
<td>Yes</td>
<td>Yes</td>
</tr>
</tbody>
</table>
<p>I&rsquo;ll leave out the discussion about temporary files in this blog post.<br>
That&rsquo;s a complex topic by itself, it raises many questions, and we have good reasons why we are still thinking about it instead of shipping a quick version prototyped with Claude.<br>
It is something I plan to talk about later, on its own.</p>
<p>Back to the comparison: it claims that pg_tde is vendor locked because it only works with our fork.<br>
I don&rsquo;t think that&rsquo;s the case:<br>
Our fork is completely open source, anybody can go to our <a href="https://github.com/percona/postgres/" target="_blank" rel="noopener noreferrer">GitHub</a> and cherry-pick our patches manually.</p>
<p>open_pg_tde doesn&rsquo;t remove the need for these patches: it can&rsquo;t, TDE requires them.<br>
It just ships them as a patch file users apply manually, rather than as a branch.<br>
Both approaches modify PostgreSQL identically.</p>
<p>The fork exists for one sole reason:<br>
convenience, we want to make the life of our users easier.<br>
Checking out a fork is easier than manually applying patch files.</p>
<p>So the difference between pg_tde and open_pg_tde is not <em>whether</em> you patch PostgreSQL, you do, either way.<br>
It&rsquo;s <em>how</em> those patches reach you.</p>
<h2 id="percona_api_version">PERCONA_API_VERSION<a class="anchor-link" id="percona_api_version"></a></h2>
<p>The open_pg_tde commit that <a href="https://github.com/commandprompt/open_pg_tde/commit/55ed7f1c4ad5bb31ab085e378699377c04158d09#diff-2b9df17a765260aa1b6bc32fedb30dee1d5f80f975252c2bf6308e5097591aac" target="_blank" rel="noopener noreferrer">removes &ldquo;vendor lock-in&rdquo;</a> is mainly a documentation change:<br>
it adds the patch file, and asks users to apply it manually.</p>
<p>It has one single code change other than that:<br>
in our upstream fork we define <code>PERCONA_API_VERSION</code> and check against it, and open_pg_tde removes both.</p>
<p>While it has PERCONA in its name, and because of that it has been misinterpreted as vendor lock-in, that isn&rsquo;t why we added it:<br>
it&rsquo;s to prevent accidents.</p>
<p>And with encryption, which modifies the storage of data, <strong>accidents might mean data corruption</strong>.</p>
<p>PostgreSQL normally is very stable:<br>
minor versions contain only bugfixes, the API/ABI is very stable, a minor upgrade is considered easy and safe.</p>
<p>However, when you start applying patches manually, you break this promise:<br>
is your patch as stable as PostgreSQL itself?</p>
<p>The reality is that it&rsquo;s not.<br>
pg_tde is backported to earlier major versions. Both pg_tde and open_pg_tde support PostgreSQL 16, and we could backport it to even earlier versions.</p>
<p>This means that the patch has to apply to multiple major versions, and if we have to make a breaking change in it?<br>
Then we have to make that change in all major versions!</p>
<p>Our API version isn&rsquo;t about locking users to our fork, it&rsquo;s a safety net.<br>
It is there to make sure that upgrades happen correctly, and our users don&rsquo;t accidentally mix pg_tde and PostgreSQL packages that aren&rsquo;t 100% compatible, but seem to work.</p>
<p>Imagine this:</p>
<ol>
<li>You have a working PostgreSQL + pg_tde installation</li>
<li>There&rsquo;s an update, you upgrade PostgreSQL</li>
<li>What you didn&rsquo;t notice is that we also updated pg_tde.<br>
You forgot to update that package, or for some reason intentionally didn&rsquo;t update it yet, and because we didn&rsquo;t break the API boundary, everything seems to work.<br>
Except, we did change some internal details of how our patch works.<br>
It doesn&rsquo;t cause any visible issues at first, but slowly some pages of your database are becoming unreadable, or you start getting segmentation faults when accessing a table&hellip;</li>
</ol>
<p>The above of course is a hypothetical scenario, we didn&rsquo;t release any dangerous update like that.</p>
<p>But the point is, even if we have to, in pg_tde, we have safeguards.<br>
In our solution the API version check catches this: at the 3rd step, instead of a slow data corruption, we present you with an early error stating that you should fix your system.</p>
<p>In the above scenario, let&rsquo;s say that in the first step you have a working installation where both the server and pg_tde have <code>PERCONA_API_VERSION=1</code>.<br>
After that, you only update the server, and the new version now has <code>PERCONA_API_VERSION=2</code>.<br>
Since the extension is still at the previous version 1, the server will fail immediately at startup, reminding you that these two packages are not compatible.</p>
<p>Maybe we could have called it something different, <code>TDE_PATCH_VERSION</code>, or something like that.<br>
But the goal is still the same, it is an important safeguard, please do not remove it!<br>
Currently open_pg_tde doesn&rsquo;t protect against this type of mismatch.</p>
<p>In fact, in our latest release, not yet included in open_pg_tde, we did increment PERCONA_API_VERSION because of a slightly incompatible change.<br>
We don&rsquo;t expect any data corruption possibilities from it, it was a very minor API change, but we want to play things safe, as keeping the data of our users secure is our first priority.</p>
<h2 id="patches-welcome">Patches welcome!<a class="anchor-link" id="patches-welcome"></a></h2>
<p>If we look into the git history of open_pg_tde, it&rsquo;s mostly (AI written) documentation changes.<br>
Other than the removal of the API check I explained above, there are two actual code changes:</p>
<ul>
<li>One is the addition of the AES-XTS algorithm for encrypting relation pages</li>
<li>Another is the support for temporary file encryption</li>
</ul>
<p>We would be happy to start a discussion of any of these features, or even others.<br>
If you want to contribute, please do not hesitate, and contact us.</p>
<p>This is the strength of open source: it allows contributions and open discussions.<br>
While the team behind open_pg_tde, or anybody else, can of course fork pg_tde this way (because we are open and do not gatekeep features), the energy spent on that could be used to make pg_tde better in a shared community effort.</p>
<p>You can open a <a href="https://github.com/percona/pg_tde/pulls" target="_blank" rel="noopener noreferrer">Pull Request</a>, or a <a href="https://github.com/percona/pg_tde/issues" target="_blank" rel="noopener noreferrer">GitHub Issue</a>, or even use our <a href="https://perconadev.atlassian.net/projects/PG/issues/" target="_blank" rel="noopener noreferrer">Jira</a>, all options are equally good!</p>
<p>Even an issue/PR stating &ldquo;please call PERCONA_API_VERSION differently&rdquo;, we are not against changing that if the consensus is that it should be something different.</p>
<p>But please don&rsquo;t create forks without even reaching out to your &ldquo;upstream&rdquo;.<br>
That only increases fragmentation, and makes the life of our common audience harder.</p>
<p>What we ask for is simple: let&rsquo;s make PostgreSQL better with our work, not harder to use.</p>
<p><figure>
<img decoding="async" src="https://percona.community/blog/2026/07/open_pg_tde_do_not_fork.png" alt="&nbsp;"></figure></p>

<p><a href="https://percona.community/blog/2026/07/08/pg_tde-our-fork-is-temporary-our-commitment-to-open-tde-is-not/">pg_tde: our fork is temporary, our commitment to open TDE is not</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Updated MariaDB C++ and ODBC Connectors now available</title>
      <link rel="alternate" type="text/html" href="https://mariadb.com/resources/blog/updated-mariadb-c-and-odbc-connectors-now-available/" />
      <id>https://mariadb.com/resources/blog/updated-mariadb-c-and-odbc-connectors-now-available/</id>
      <updated>2026-07-07T15:23:24+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++ 1.1.8 and 1.0.7 and MariaDB Connector/ODBC 3.2.9 and 3.1.23. […]</p>
<p><a href="https://mariadb.com/resources/blog/updated-mariadb-c-and-odbc-connectors-now-available/">Updated MariaDB C++ and ODBC Connectors now available</a> appeared first on <a 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++ 1.1.8 and 1.0.7 and MariaDB Connector/ODBC 3.2.9 and 3.1.23. All are Stable (GA) releases. Download Now 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/updated-mariadb-c-and-odbc-connectors-now-available/" rel="nofollow">Source</a></p>

<p><a href="https://mariadb.com/resources/blog/updated-mariadb-c-and-odbc-connectors-now-available/">Updated MariaDB C++ and ODBC Connectors now available</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Percona Operator for MySQL 1.2.0: Cross-Site Replication, Encrypted Backups, and Automatic Storage Scaling</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/percona-operator-for-mysql-1-2-0-cross-site-replication-encrypted-backups-storage-autoscaling/" />
      <id>https://www.percona.com/blog/percona-operator-for-mysql-1-2-0-cross-site-replication-encrypted-backups-storage-autoscaling/</id>
      <updated>2026-07-07T13:19:26+00:00</updated>
      <author><name>Slava Sarzhan</name></author>
      <summary type="html"><![CDATA[<p>  Percona Operator for MySQL 1.2.0 is out, and it closes three gaps that platform teams hit once a MySQL deployment grows past a single cluster. Picture a fleet that has outgrown one region: you want a warm replica cluster in a second data center, backups in object storage that pass an auditor’s encryption check, … Continued<br />
The post Percona Operator for MySQL 1.2.0: Cross-Site Replication, Encrypted Backups, and Automatic Storage Scaling appeared first on Percona.</p>
<p><a href="https://www.percona.com/blog/percona-operator-for-mysql-1-2-0-cross-site-replication-encrypted-backups-storage-autoscaling/">Percona Operator for MySQL 1.2.0: Cross-Site Replication, Encrypted Backups, and Automatic Storage Scaling</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><img loading="lazy" decoding="async" class="aligncenter wp-image-50188 size-large" src="https://www.percona.com/wp-content/uploads/2026/07/Hero-2400-x-880-1024x376.png" alt="" width="1024" height="376"></p>
<p>&nbsp;</p>
<p><b>Percona Operator for MySQL 1.2.0</b><span style="font-weight: 400"> is out, and it closes three gaps that platform teams hit once a MySQL deployment grows past a single cluster. Picture a fleet that has outgrown one region: you want a warm replica cluster in a second data center, backups in object storage that pass an auditor&rsquo;s encryption check, and volumes that grow before they fill at 3 a.m. Until now, each of those meant scripting around the operator. This release brings all three into the custom resource.</span></p>
<p><span style="font-weight: 400">The three headline features are </span><b>cross-site replication for Group Replication</b><span style="font-weight: 400">, </span><b>encrypted backups</b><span style="font-weight: 400">, and </span><b>automatic storage scaling</b><span style="font-weight: 400">. Each one turns a manual, error-prone procedure into a declarative field you set once and let the operator reconcile.</span></p>
<p><span style="font-weight: 400">The operator is open source and runs on any CNCF-certified Kubernetes distribution. Many of the changes in this release come straight from what users asked for on </span><a href="https://forums.percona.com/"><span style="font-weight: 400">forums.percona.com</span></a><span style="font-weight: 400"> and in the public issue tracker, from disaster-recovery topologies to backup encryption to storage that keeps up with data growth.</span></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">Cross-site replication for Group Replication clusters</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Encrypted backups to S3, GCS, and Azure</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Automatic storage scaling for MySQL data volumes</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Other improvements worth knowing about</span></li>
</ul>
<h2><b><br>
Cross-site replication for Group Replication</b><a class="anchor-link" id="cross-site-replication-for-group-replication"></a></h2>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-50189 size-large" src="https://www.percona.com/wp-content/uploads/2026/07/clusterset-topology-1024x551.png" alt="" width="1024" height="551"></p>
<p>&nbsp;</p>
<p><span style="font-weight: 400">Group Replication gives you a self-healing, multi-primary-capable cluster inside one Kubernetes cluster. What it does not give you on its own is a second site. If the region hosting your cluster goes down, Group Replication cannot fail over to hardware it does not know about. Teams have solved this by hand-wiring asynchronous replication between clusters and babysitting it, which is exactly the kind of stateful glue an operator should own.</span></p>
<p>&nbsp;</p>
<h3><b>Why it matters</b><a class="anchor-link" id="why-it-matters"></a></h3>
<p><span style="font-weight: 400">A disaster-recovery topology is only useful if it is reproducible and observable. Hand-built replication links drift: someone changes a credential, a channel stalls, and nobody notices until the failover that was supposed to save you does not work. Declaring the topology as a Kubernetes object means the operator reconciles it continuously, and the same manifest recreates it in staging, in a runbook test, and in the real event.</span></p>
<p>&nbsp;</p>
<h3><b>How it works</b><a class="anchor-link" id="how-it-works"></a></h3>
<p><span style="font-weight: 400">The operator adds a new custom resource, </span><strong>PerconaServerMySQLClusterSet</strong><span style="font-weight: 400">, that groups two or more Group Replication clusters into a single set with one primary. The operator drives MySQL Shell to build the InnoDB ClusterSet, wires the replica clusters to the primary, and tracks the topology in the resource&rsquo;s status. A replica cluster provisions from the primary using a chosen recovery method, so you do not stage data manually.</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: ps.percona.com/v1
kind: PerconaServerMySQLClusterSet
metadata:
  name: my-cluster-set
  finalizers:
    - percona.com/clusterset-dissolve
spec:
#  unsafeFlags:
#    forcedFailover: false
#    forcedClusterRemoval: false
  primaryCluster: pscluster1
  credentialsSecret:
    name: ps-cluster1-secrets
    key: clusterset
  sslMode: AUTO
  createReplicaClusterOptions:
    recoveryMethod: clone
  clusters:
    - innodbClusterName: pscluster1
      endpoints:
      - host: ps-cluster1-mysql-primary.default.svc.cluster.local
    - innodbClusterName: pscluster2
      endpoints:
      - host: ps-cluster2-mysql-0.ps-cluster2-mysql.default.svc.cluster.local
  mysqlshellRunner:
    image: percona/percona-server:8.4.10-10.1</pre>
<p><strong>primaryCluster</strong><span style="font-weight: 400"> names the source of truth. Each entry under </span><strong>clusters</strong><span style="font-weight: 400"> points at a Group Replication cluster by its InnoDB cluster name and reachable endpoints, so the clusters can live in separate namespaces or separate Kubernetes clusters joined by routable DNS. </span><strong>createReplicaClusterOptions.recoveryMethod: clone</strong><span style="font-weight: 400"> tells the replica to seed itself with a full clone. The </span><strong>percona.com/clusterset-dissolve</strong><span style="font-weight: 400"> finalizer ensures the operator tears the ClusterSet down cleanly instead of leaving orphaned replication channels behind.</span><br>
&nbsp;</p>
<h3><b>Failover and cleanup</b><a class="anchor-link" id="failover-and-cleanup"></a></h3>
<p><span style="font-weight: 400">Once the set exists, the operator keeps the replica clusters attached to the primary and surfaces the topology in the resource&rsquo;s status, so you can see which cluster is primary and whether every replica is connected without shelling into MySQL Shell. A planned switchover promotes a replica to primary. The </span><span style="font-weight: 400">unsafeFlags</span><span style="font-weight: 400"> block gates the disruptive paths for when the primary is already gone.</span></p>
<blockquote>
<p><b>Note:</b><span style="font-weight: 400"> The unsafeFlags block gates disruptive operations such as forced failover and forced cluster removal. Leave these off for normal operation and reach for them only in a controlled recovery, since a forced failover can diverge history if the old primary comes back.</span></p>
</blockquote>
<p>&nbsp;</p>
<h2><b>Encrypted backups</b><a class="anchor-link" id="encrypted-backups"></a></h2>
<p><span style="font-weight: 400">Backups are the copy of your data most likely to leave the cluster&rsquo;s security boundary. They land in an object-storage bucket, get replicated across a provider&rsquo;s regions, and often live longer than the database that produced them. If they are not encrypted before they leave the pod, a bucket misconfiguration or a leaked credential exposes the whole dataset. This release lets the operator encrypt backup data as it is </span></p>
<p>&nbsp;</p>
<h3><b>How it works</b><a class="anchor-link" id="how-it-works"></a></h3>
<p><span style="font-weight: 400">Backups in the operator run on </span><a href="https://docs.percona.com/percona-xtrabackup/8.4/"><span style="font-weight: 400">Percona XtraBackup</span></a><span style="font-weight: 400">. XtraBackup encrypts the stream with its </span><span style="font-weight: 400">xbcrypt</span><span style="font-weight: 400"> component before uploading, so the data is ciphertext at rest in the bucket and stays that way until you restore it with the same key. You supply the key through a Kubernetes Secret and reference that Secret from the backup configuration. The operator never bakes the key into a manifest or an image.</span></p>
<p>&nbsp;</p>
<h3><b>Wiring it up</b><a class="anchor-link" id="wiring-it-up"></a></h3>
<p><span style="font-weight: 400">Point a storage target at an encryption-key Secret with </span><strong>encryptionKeySecret</strong><span style="font-weight: 400">:</span></p>
<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: ps.percona.com/v1
kind: PerconaServerMySQL
metadata:
  name: cluster1
spec:
  backup:
    storages:
      s3-us-west:
        type: s3
        encryptionKeySecret:
          key: encryptionKey
          name: my-s3-encryption-key-secret
        s3:
          bucket: S3-BACKUP-BUCKET-NAME-HERE
          credentialsSecret: cluster1-s3-credentials
          region: us-west-2</pre>
<p><span style="font-weight: 400">The same </span><strong>encryptionKeySecret</strong><span style="font-weight: 400"> field works under S3, GCS, and Azure storage targets, so a multi-cloud backup policy uses one consistent mechanism. You can also set an </span><strong>encryptionKeySecret</strong><span style="font-weight: 400"> at the </span><strong>backup</strong><span style="font-weight: 400"> level to apply one key across every storage target instead of repeating it per bucket. The referenced Secret holds the key under the </span><strong>encryptionKey</strong><span style="font-weight: 400"> data field.</span></p>
<p>&nbsp;</p>
<h3><b>Restoring an encrypted backup</b><a class="anchor-link" id="restoring-an-encrypted-backup"></a></h3>
<p><span style="font-weight: 400">Encryption is transparent on the way back in. When you restore, the operator reads the same Secret, hands the key to XtraBackup, and decrypts the stream before it prepares the data directory. The only hard requirement is that the key still exists: the restore fails fast if the Secret is missing or holds a different key than the one that produced the backup. Encryption also composes with backup compression, so you keep the smaller footprint and the ciphertext-at-rest guarantee at the same time.</span></p>
<blockquote>
<p><b>Note:</b><span style="font-weight: 400"> Keep the encryption key safe and versioned outside the cluster. A backup encrypted with a key you have lost is not recoverable. Treat the key with the same care as the backups themselves.</span></p>
</blockquote>
<p>&nbsp;</p>
<h2><b>Automatic storage scaling</b><a class="anchor-link" id="automatic-storage-scaling"></a></h2>
<p><span style="font-weight: 400">Running out of disk is one of the fastest ways to take a database down, and it rarely happens at a convenient hour. The operator has supported manual volume expansion since an earlier release: you raise </span><strong>resources.requests.storage</strong><span style="font-weight: 400">, apply, and the operator grows the <strong>PersistentVolumeClaim</strong> for you. That still requires a human to notice the trend and act. Version 1.2.0 adds automatic scaling that watches usage and grows the volume on its own.</span></p>
<p>&nbsp;</p>
<h3><b>Why it matters</b><a class="anchor-link" id="why-it-matters"></a></h3>
<p><span style="font-weight: 400">Storage growth is predictable in aggregate and unpredictable in timing. A batch import, a retention change, or an unexpected traffic spike can eat headroom faster than an on-call engineer can respond. Letting the operator resize before the volume fills turns a page someone at 3 a.m. incident into a log line, as long as your storage class supports online expansion.</span></p>
<p>&nbsp;</p>
<h3><b>How it works</b><a class="anchor-link" id="how-it-works"></a></h3>
<p><span style="font-weight: 400">You enable volume expansion, then define an autoscaling policy. The operator monitors each data PVC and, when usage crosses the threshold, grows the volume by a fixed step up to a ceiling you set. Because it builds on the Kubernetes volume-expansion API, the underlying storage class must have </span><strong>AllowVolumeExpansion: true</strong><span style="font-weight: 400">.</span></p>
<p>&nbsp;</p>
<h3><b>Wiring it up</b><a class="anchor-link" id="wiring-it-up"></a></h3>

<pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: ps.percona.com/v1
kind: PerconaServerMySQL
metadata:
  name: cluster1
spec:
  enableVolumeExpansion: true
  storageScaling:
    enableVolumeScaling: true
    autoscaling:
      enabled: true
      growthStep: 2Gi
      maxSize: 10Gi
      triggerThresholdPercent: 80</pre>
<p>&nbsp;</p>
<p><strong>triggerThresholdPercent</strong><span style="font-weight: 400"> is the fill level that triggers a resize (default </span><span style="font-weight: 400">80</span><span style="font-weight: 400">, allowed range 50 to 95). </span><strong>growthStep</strong><span style="font-weight: 400"> is how much capacity each resize adds (default </span><strong>2Gi</strong><span style="font-weight: 400">), and </span><strong>maxSize</strong><span style="font-weight: 400"> caps total growth so a runaway workload cannot expand a volume without bound. The operator validates the relationship between these fields: </span><strong>autoscaling</strong><span style="font-weight: 400"> cannot be enabled unless </span><strong>enableVolumeScaling</strong><span style="font-weight: 400"> is on. For teams that prefer an external controller to own resizing, </span><strong>enableExternalAutoscaling</strong><span style="font-weight: 400"> hands that responsibility off instead.</span></p>
<p><span style="font-weight: 400">The operator records each resize in the cluster status under </span><strong>storageAutoscaling</strong><span style="font-weight: 400">, including the count of resizes and the timestamp of the last one. That gives you an audit trail and a signal worth alerting on: a volume that keeps hitting its </span><strong>growthStep</strong><span style="font-weight: 400"> is telling you the workload has changed, and a volume approaching </span><strong>maxSize</strong><span style="font-weight: 400"> is telling you to plan capacity before the ceiling stops the next resize.</span></p>
<blockquote>
<p><b>Note: </b><span style="font-weight: 400">PVC expansion is one-way. Kubernetes can grow a volume but cannot shrink it, so set maxSize deliberately. Confirm your storage class allows expansion before you rely on this in production.</span></p>
<p>&nbsp;</p>
</blockquote>
<h2><b>Other improvements</b><a class="anchor-link" id="other-improvements"></a></h2>
<p><span style="font-weight: 400">Beyond the three headline features, 1.2.0 ships a set of enhancements that smooth day-two operations:</span></p>
<ul>
<li style="font-weight: 400"><b>Dedicated root user Secret</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPS-689"><span style="font-weight: 400">K8SPS-689</span></a><span style="font-weight: 400">): the operator publishes root connection details in a dedicated Secret, so applications and tooling read one predictable object instead of parsing several.</span></li>
<li style="font-weight: 400"><b>Disable NodePort allocation for LoadBalancer Services</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPS-496"><span style="font-weight: 400">K8SPS-496</span></a><span style="font-weight: 400">): set </span><span style="font-weight: 400">allocateLoadBalancerNodePorts: false</span><span style="font-weight: 400"> to stop Kubernetes from opening NodePorts you never use behind a cloud load balancer.</span></li>
<li style="font-weight: 400"><b>Custom cluster naming for PMM</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPS-627"><span style="font-weight: 400">K8SPS-627</span></a><span style="font-weight: 400">): give a cluster a stable display name so multi-region and multi-namespace fleets stay legible in </span><a href="https://docs.percona.com/percona-monitoring-and-management/"><span style="font-weight: 400">Percona Monitoring and Management</span></a><span style="font-weight: 400">.</span></li>
<li style="font-weight: 400"><b>Vault encryption Secret validation</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPS-487"><span style="font-weight: 400">K8SPS-487</span></a><span style="font-weight: 400">): the operator validates the Vault Secret and reports problems in status immediately, instead of failing later during an operation.</span></li>
<li style="font-weight: 400"><b>Concurrent reconciliation</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPS-434"><span style="font-weight: 400">K8SPS-434</span></a><span style="font-weight: 400">): tune how many clusters the operator reconciles at once through an environment variable, which helps a single operator manage a larger fleet.</span></li>
<li style="font-weight: 400"><b>Independent </b><b>mysql-monit</b><b> sidecar resources</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPS-742"><span style="font-weight: 400">K8SPS-742</span></a><span style="font-weight: 400">): set CPU and memory for the monitoring sidecar separately from the database container.</span></li>
<li style="font-weight: 400"><b>Orchestrator API authentication</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPS-19"><span style="font-weight: 400">K8SPS-19</span></a><span style="font-weight: 400">): the Orchestrator API now requires valid credentials.</span></li>
<li style="font-weight: 400"><b>Binlog storage configuration in restore objects</b><span style="font-weight: 400"> (</span><a href="https://perconadev.atlassian.net/browse/K8SPS-716"><span style="font-weight: 400">K8SPS-716</span></a><span style="font-weight: 400">): point a restore at the binlog storage it needs for point-in-time recovery.</span></li>
</ul>
<p><span style="font-weight: 400">For the full list, including bug fixes, see the release notes linked below.</span></p>
<p>&nbsp;</p>
<h2><b>Conclusion</b><a class="anchor-link" id="conclusion"></a></h2>
<p><span style="font-weight: 400">Percona Operator for MySQL 1.2.0 extends the operator across the parts of the lifecycle that used to need custom glue: replication across sites, encryption of data that leaves the cluster, and storage that keeps up with growth. Platform teams running MySQL fleets on Kubernetes get declarative control over disaster recovery, a cleaner path through a security review, and one less 3 a.m. page. If there is a topology or a control you still have to script around, tell us on the forum, since that feedback is where releases like this one come from.</span></p>
<p>&nbsp;</p>
<h2><b>Try Percona Operator for MySQL 1.2.0</b><a class="anchor-link" id="try-percona-operator-for-mysql-1-2-0"></a></h2>
<ul>
<li style="font-weight: 400"><b>Release notes</b><span style="font-weight: 400">: </span><a href="https://docs.percona.com/percona-operator-for-mysql/ps/ReleaseNotes/Kubernetes-Operator-for-PS-RN1.2.0.html"><span style="font-weight: 400">Percona Operator for MySQL 1.2.0 Release Notes</span></a></li>
<li style="font-weight: 400"><b>Documentation</b><span style="font-weight: 400">: </span><a href="https://docs.percona.com/percona-operators/"><span style="font-weight: 400">Percona Operator for MySQL docs</span></a></li>
<li style="font-weight: 400"><b>GitHub</b><span style="font-weight: 400">: </span><a href="https://github.com/percona/percona-server-mysql-operator"><span style="font-weight: 400">percona/percona-server-mysql-operator</span></a></li>
<li style="font-weight: 400"><b>Community Forum</b><span style="font-weight: 400">: </span><a href="https://forums.percona.com/c/mysql-mariadb/percona-kubernetes-operator-for-mysql/28"><span style="font-weight: 400">forums.percona.com</span></a><span style="font-weight: 400">: share your feedback, ask questions, or report issues</span></li>
</ul>
<p>&nbsp;</p>
<p>The post <a href="https://www.percona.com/blog/percona-operator-for-mysql-1-2-0-cross-site-replication-encrypted-backups-storage-autoscaling/">Percona Operator for MySQL 1.2.0: Cross-Site Replication, Encrypted Backups, and Automatic Storage Scaling</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p><a href="https://www.percona.com/blog/percona-operator-for-mysql-1-2-0-cross-site-replication-encrypted-backups-storage-autoscaling/">Percona Operator for MySQL 1.2.0: Cross-Site Replication, Encrypted Backups, and Automatic Storage Scaling</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Comparing Migration Methods from the Crunchy Data PostgreSQL Operator to the Percona Operator for PostgreSQL</title>
      <link rel="alternate" type="text/html" href="https://www.percona.com/blog/comparing-migration-methods-from-the-crunchy-data-postgresql-operator-to-the-percona-operator-for-postgresql/" />
      <id>https://www.percona.com/blog/comparing-migration-methods-from-the-crunchy-data-postgresql-operator-to-the-percona-operator-for-postgresql/</id>
      <updated>2026-07-07T13:01:43+00:00</updated>
      <author><name>Chetan Shivashankar</name></author>
      <summary type="html"><![CDATA[<p>Migrating a production PostgreSQL database on Kubernetes is not only about moving data from one operator to another. It is also about choosing the right trade-off between downtime, operational complexity, rollback safety, cost, and business risk. Practical migration paths from the Crunchy Data PostgreSQL Operator to the Percona Operator for PostgreSQL are described here.  1. … Continued<br />
The post Comparing Migration Methods from the Crunchy Data PostgreSQL Operator to the Percona Operator for PostgreSQL appeared first on Percona.</p>
<p><a href="https://www.percona.com/blog/comparing-migration-methods-from-the-crunchy-data-postgresql-operator-to-the-percona-operator-for-postgresql/">Comparing Migration Methods from the Crunchy Data PostgreSQL Operator to the Percona Operator for PostgreSQL</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><span style="font-weight: 400">Migrating a production PostgreSQL database on Kubernetes is not only about moving data from one operator to another. It is also about choosing the right trade-off between downtime, operational complexity, rollback safety, cost, and business risk.</span></p>
<p><span style="font-weight: 400">Practical migration paths from the Crunchy Data PostgreSQL Operator to the Percona Operator for PostgreSQL are described </span><a href="https://docs.percona.com/percona-operator-for-postgresql/3.0.0/migrate-from-crunchy.html"><span style="font-weight: 400">here</span></a><span style="font-weight: 400">.&nbsp;</span></p>
<h4><b>1. Migration to standby cluster utilizing the same pgBackRest repository</b></h4>
<p><span style="font-weight: 400">In this method, the Percona cluster is created as a standby and points to the same pgBackRest repository used by the Crunchy Data PostgreSQL Operator cluster. This means the object storage and the path are exactly the same for both clusters.</span></p>
<p><span style="font-weight: 400">With the above configuration, the Percona standby restores the initial backup from the shared repository and replays archived WAL. The method is described </span><a href="https://docs.percona.com/percona-operator-for-postgresql/3.0.0/standby-backup.html#configure-dr-site"><span style="font-weight: 400">here</span></a><span style="font-weight: 400">.</span></p>
<h4><strong>2.Migration to Standby Cluster with Streaming Replication</strong></h4>
<p><strong>&nbsp;&nbsp;</strong><span style="font-weight: 400">In this approach, the standby cluster initiates a </span><a href="https://www.postgresql.org/docs/current/app-pgbasebackup.html"><span style="font-weight: 400">pg_basebackup</span></a><span style="font-weight: 400"> for the initial restore and uses native postgres streaming for sync. The method is described </span><a href="https://docs.percona.com/percona-operator-for-postgresql/3.0.0/standby-streaming.html"><span style="font-weight: 400">here</span></a><span style="font-weight: 400">.</span></p>
<h4><b>3.Backup and Restore&nbsp;</b></h4>
<p><span style="font-weight: 400">In this method, a backup is taken from the Crunchy cluster and restored to a cluster managed by the Percona Operator for PostgreSQL.</span></p>
<h4><b>4.Migration by reusing the persistent volume</b></h4>
<p><span style="font-weight: 400">In this method, the existing Crunchy Data PostgreSQL Operator primary&rsquo;s PGDATA persistent volume is used. The process is: stop writes to the Crunchy Data PostgreSQL Operator cluster, delete the cluster while retaining the persistent volume, clear the old persistent volume claim reference, and create a Percona cluster whose PVC selector binds to that same retained PV. PostgreSQL then starts on the existing data directory without a restore.</span></p>
<p><span style="font-weight: 400">Each method works, but each one is suitable for a different operational scenario. A migration strategy that is ideal for a small development database may be risky for a large production system with heavy write traffic. Similarly, a method that gives the lowest downtime may require more preparation and validation.</span></p>
<p><span style="font-weight: 400">This post compares the four migration approaches so users can choose the most suitable approach.</span></p>
<h2><span style="font-weight: 400">Factors to Consider Before Migration</span><a class="anchor-link" id="factors-to-consider-before-migration"></a></h2>
<p><span style="font-weight: 400">There is no single best migration strategy for every PostgreSQL workload. A production migration usually has to balance several requirements:</span></p>
<ul>
<li style="font-weight: 400"><span style="font-weight: 400">How Much Downtime is Acceptable?</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">What is the Network Connectivity Status Between the Clusters?</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">What is the RTO/RPO When Something Goes Wrong?</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">How Large is the Database?</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">How Write-Heavy is the Workload?</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">How Quickly Must the System be Rolled Back?</span></li>
<li style="font-weight: 400"><span style="font-weight: 400">Is the Migration Happening Across Namespaces, Clusters, Storage Classes, or Cloud?</span></li>
</ul>
<h2><span style="font-weight: 400">Comparison of Migration Methods</span><a class="anchor-link" id="comparison-of-migration-methods"></a></h2>
<table dir="ltr" border="1" cellspacing="0" cellpadding="0" data-sheets-root="1" data-sheets-baot="1">
<colgroup>
<col width="157">
<col width="259">
<col width="235">
<col width="224">
<col width="227"></colgroup>
<tbody>
<tr>
<td></td>
<td>Same pgBackRest Repository</td>
<td>Streaming Replication</td>
<td>Backup and Restore</td>
<td>Reuse the Persistent volume</td>
</tr>
<tr>
<td>Implementation</td>
<td>Primary archives WAL &rarr; shared repo &rarr; standby does a restore + fetches WAL via pgBackRest archive-get</td>
<td>Primary streams WAL over TCP &rarr; standby pg_basebackup + WAL receiver</td>
<td>Take backup from primary -&gt; Restore to new cluster</td>
<td>Same volume is reused by the Percona Operator for PostgreSQL</td>
</tr>
<tr>
<td>Initial Seed</td>
<td>Restore from an existing backup with pgBackRest</td>
<td>pg_basebackup from live primary</td>
<td>Restore from an existing backup with pgBackRest</td>
<td>Volume contains the entire data</td>
</tr>
<tr>
<td>Primary to standby sync</td>
<td>WAL is fetched with pgBackRest archive-get</td>
<td>Native PostgreSQL streaming</td>
<td>WAL is fetched with pgBackRest archive-get</td>
<td>N/A</td>
</tr>
<tr>
<td>Performance impact to the primary</td>
<td>No extra impact</td>
<td>Slight impact due to the pg_basebackup and streaming</td>
<td>No extra impact</td>
<td>Primary will be down for the duration of the migration</td>
</tr>
<tr>
<td>Network dependencies</td>
<td>No network connectivity is required between the primary and standby clusters.</td>
<td>Network connectivity needed between primary and standby nodes.</td>
<td>No network connectivity is required between the primary and standby clusters.</td>
<td>No Dependency</td>
</tr>
<tr>
<td>Object storage dependency</td>
<td>Object storage used for WAL should be accessible by both primary and standby</td>
<td>No dependency</td>
<td>Object storage used for WAL should be accessible by both primary and standby</td>
<td>No dependency</td>
</tr>
<tr>
<td>Downtime</td>
<td>During cutover from the primary to the standby, writes must be blocked until the standby catches up with the primary, or the cutover should be performed during a period of low write activity to allow the standby to catch up.</td>
<td>During cutover from the primary to the standby, writes must be blocked until the standby catches up with the primary, or the cutover should be performed during a period of low write activity to allow the standby to catch up.</td>
<td>During cutover from the primary to the standby, writes must be blocked until the standby catches up with the primary, or the cutover should be performed during a period of low write activity to allow the standby to catch up.</td>
<td>Complete downtime till the new cluster is started</td>
</tr>
<tr>
<td>Rollback</td>
<td>Use the older crunchy cluster.<br>
Easy if there were no writes done on standby. If there were any writes done on standby, data consistency needs to be checked before rolling back.</td>
<td>Use the older crunchy cluster.<br>
Easy if there were no writes done on standby. If there were any writes done on standby, data consistency needs to be checked before rolling back</td>
<td>Use the older crunchy cluster.<br>
Easy if there were no writes done on standby. If there were any writes done on standby, data consistency needs to be checked before rolling back</td>
<td>Easy to rollback; no issues with data inconsistency, unless the data volume get&rsquo;s corrupted.</td>
</tr>
<tr>
<td>Business continuity risks</td>
<td>If migration fails for some reason, it is easy to fall back to the Crunchy Data PostgreSQL Operator cluster.</td>
<td>If migration fails for some reason, it is easy to fall back to the Crunchy Data PostgreSQL Operator cluster.</td>
<td>If migration fails for some reason, it is easy to fall back to the Crunchy Data PostgreSQL Operator cluster.</td>
<td>Can fallback to using the Crunchy Data PostgreSQL Operator cluster if migration fails. If the data volume gets corrupted, full restore needs to be done from backup. RTO/RPO depends on the dataset size and the WAL pushed to the object storage.</td>
</tr>
<tr>
<td>Cost /Resources utilization</td>
<td>For the duration of migration, there will be 2 clusters which adds up to the resources and the cost.</td>
<td>For the duration of migration, there will be 2 clusters which adds up to the resources and the cost.</td>
<td>For the duration of migration, there will be 2 clusters which adds up to the resources and the cost.</td>
<td>No additional resources / cost needed.</td>
</tr>
<tr>
<td>Compatible with other / custom backup solution</td>
<td>Backups should be taken with pgbackrest</td>
<td>Any backup solution can be used by the Crunchy Data PostgreSQL Operator</td>
<td>Backups should be taken with pgbackrest</td>
<td>Any backup solution can be used by the Crunchy Data PostgreSQL Operator</td>
</tr>
<tr>
<td>Client DNS caching issues</td>
<td>Clients might refer to the older entry due to caching entries. This will be reflected for short period of TTL expiry or caching rule set at client after the migration</td>
<td>Clients might refer to the older entry due to caching entries. This will be reflected for short period of TTL expiry or caching rule set at client after the migration</td>
<td>Clients might refer to the older entry due to caching entries. This will be reflected for short period of TTL expiry or caching rule set at client after the migration</td>
<td>No issues</td>
</tr>
<tr>
<td>Migrating to different kubernetes cluster</td>
<td>Possible</td>
<td>Possible</td>
<td>Possible</td>
<td>Not possible</td>
</tr>
</tbody>
</table>
<h2><a class="anchor-link" id=""></a></h2>
<h2><span style="font-weight: 400">Conclusion</span><a class="anchor-link" id="conclusion"></a></h2>
<p><span style="font-weight: 400">There is no &ldquo;one-size-fits-all&rdquo; solution for migrating PostgreSQL workloads on Kubernetes. Choosing the right strategy requires a careful balance between acceptable downtime, operational complexity, and business continuity requirements. For example, below there are some scenarios which list the suitable approaches</span></p>
<ul>
<li style="font-weight: 400"><b>For scenarios where zero downtime is not strictly required but minimal operational impact is preferred</b><span style="font-weight: 400">, migration via a shared pgBackRest repository could be a feasible solution.</span></li>
<li style="font-weight: 400"><b>For environments where network latency is low and real-time synchronization is feasible</b><span style="font-weight: 400">, Streaming Replication could be a feasible solution.</span></li>
<li style="font-weight: 400"><b>For simpler migrations where network connectivity between clusters is not feasible</b><span style="font-weight: 400">, a standard Backup and Restore will work.</span></li>
<li style="font-weight: 400"><b>For rapid migrations involving massive datasets where storage mobility is not required</b><span style="font-weight: 400">, reusing the existing persistent volume can significantly reduce migration time.</span></li>
</ul>
<p><span style="font-weight: 400">Before proceeding, we recommend conducting a dry run in a staging environment to validate your chosen method against your specific network topology and workload requirements. By carefully evaluating these trade-offs, you can ensure a secure, efficient transition to the Percona Operator for PostgreSQL.</span></p>
<p>The post <a href="https://www.percona.com/blog/comparing-migration-methods-from-the-crunchy-data-postgresql-operator-to-the-percona-operator-for-postgresql/">Comparing Migration Methods from the Crunchy Data PostgreSQL Operator to the Percona Operator for PostgreSQL</a> appeared first on <a href="https://www.percona.com">Percona</a>.</p>

<p><a href="https://www.percona.com/blog/comparing-migration-methods-from-the-crunchy-data-postgresql-operator-to-the-percona-operator-for-postgresql/">Comparing Migration Methods from the Crunchy Data PostgreSQL Operator to the Percona Operator for PostgreSQL</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>TAF 3.0 — Results Backend With Automated Performance Change Detection</title>
      <link rel="alternate" type="text/html" href="https://mariadb.org/taf-3-0-results-backend-with-automated-performance-change-detection/" />
      <id>https://mariadb.org/taf-3-0-results-backend-with-automated-performance-change-detection/</id>
      <updated>2026-07-07T10:55:01+00:00</updated>
      <author><name>Jonathan Miller</name></author>
      <summary type="html"><![CDATA[<p>TAF 3.0 introduces the new TAF Results Backend, a structured results database and parser pipeline that delivers fully automated performance change detection. …<br />
Continue reading \"TAF 3.0 — Results Backend With Automated Performance Change Detection\"<br />
The post TAF 3.0 — Results Backend With Automated Performance Change Detection appeared first on MariaDB.org.</p>
<p><a href="https://mariadb.org/taf-3-0-results-backend-with-automated-performance-change-detection/">TAF 3.0 — Results Backend With Automated Performance Change Detection</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><a href="https://github.com/MariaDB/TAF">TAF</a> 3.0 introduces the new <a href="https://github.com/MariaDB/TAF">TAF</a> Results Backend, a structured results database and parser pipeline that delivers fully automated performance change detection. &hellip; </p>
<p class="link-more"><a href="https://mariadb.org/taf-3-0-results-backend-with-automated-performance-change-detection/" class="more-link">Continue reading<span class="screen-reader-text"> &ldquo;TAF 3.0 &mdash; Results Backend With Automated Performance Change Detection&rdquo;</span></a></p>
<p>The post <a rel="nofollow" href="https://mariadb.org/taf-3-0-results-backend-with-automated-performance-change-detection/">TAF 3.0 &mdash; Results Backend With Automated Performance Change Detection</a> appeared first on <a rel="nofollow" href="https://mariadb.org">MariaDB.org</a>.</p>

<p><a href="https://mariadb.org/taf-3-0-results-backend-with-automated-performance-change-detection/">TAF 3.0 — Results Backend With Automated Performance Change Detection</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <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><a 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 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><a 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 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><a href="https://mariadb.org/mariadb-server-plugins-disabled-functions/">MariaDB Server Plugins: disabled functions</a> appeared first on <a 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><a href="https://mariadb.org/mariadb-server-plugins-disabled-functions/">MariaDB Server Plugins: disabled functions</a> appeared first on <a 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><a 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 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><a 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 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><a 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 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><a 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 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><a 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 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><a 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 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><a 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 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><a 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 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><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://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><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://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><a href="https://percona.community/blog/2026/07/01/postgresql-autovacuum-internals-benchmark/">PostgreSQL Autovacuum Internals and Benchmark</a> appeared first on <a 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><a href="https://percona.community/blog/2026/07/01/postgresql-autovacuum-internals-benchmark/">PostgreSQL Autovacuum Internals and Benchmark</a> appeared first on <a 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><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://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 loading="lazy" decoding="async" 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 loading="lazy" decoding="async" 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><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://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><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://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><img loading="lazy" decoding="async" 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><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://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><a href="https://www.percona.com/blog/debugging-with-ephemeral-containers/">Debugging with Ephemeral Containers</a> appeared first on <a 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><a href="https://www.percona.com/blog/debugging-with-ephemeral-containers/">Debugging with Ephemeral Containers</a> appeared first on <a 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><a 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 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><a 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 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><a 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 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><a 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 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><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://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><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://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><a 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 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><a 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 href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Preparing Your Analytical Databases for Agents</title>
      <link rel="alternate" type="text/html" href="https://vettabase.com/preparing-your-analytical-databases-for-agents/" />
      <id>https://vettabase.com/preparing-your-analytical-databases-for-agents/</id>
      <updated>2026-06-27T15:15:52+00:00</updated>
      <author><name>Federico Razzoli</name></author>
      <summary type="html"><![CDATA[<p>Agents can query databases: this has always been the dream of many data people! But there is some work to do to make this process reliable, secure, and valuable for your team.</p>
<p><a href="https://vettabase.com/preparing-your-analytical-databases-for-agents/">Preparing Your Analytical Databases for Agents</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p class="wp-block-paragraph">Agents can query databases: this has always been the dream of many data people! Sure, SQL resembles English, so it makes it relatively easy to express your questions and get answers. The key here is <em>relatively</em>. You still need to think about your schema, which tables need to be joined to gather the information you need, recall column names, and follow a precise syntax.</p>
<p class="wp-block-paragraph">Agents save you that effort. You express your query in English, you get a result. Well, you might still have to wait if you have a lot of data and the query is complex, but during that time you can switch to other tasks. It feels like waving a magic wand and seeing your desires materialise&hellip; but it&rsquo;s not exactly like that. Because both you and LLMs can make mistakes, especially if your schema isn&rsquo;t clear.</p>
<p class="wp-block-paragraph">And that&rsquo;s why I tell people that their schemas should be prepared for agents. Let&rsquo;s see what this means.</p>
<h2 class="wp-block-heading">Security<a class="anchor-link" id="security"></a></h2>
<p class="wp-block-paragraph">The first thing to address is security. Make sure that agents and tools that read data can only access data they should be able to access, and they can only perform the authorised operations. For example, typically they must not be able to add, modify or delete any data. And if your database contains PII data (such as customer names, anagraphic data and contacts), you typically don&rsquo;t want your AI to be able to query those data.</p>
<p class="wp-block-paragraph">In some cases, the data that the agent or tool should be able to see depend on the user. A sales representative might be authorised to see her own customers and performance data, but not her colleagues data. A member of the legal team might be authorised to see contracts that other users shouldn&rsquo;t see.</p>
<p class="wp-block-paragraph">Databases have all the features you need to implement this level of security. It&rsquo;s important that the agent or tool uses a dedicated database user, or if necessary, a database user that is dedicated to the AI&rsquo;s current human user. Databases support different permissions for every type of supported operations &ndash; typically, you only want to grant AI the <code>SELECT</code> permission. That permission can be granted at schema, table, or column level. Implementing row-level permission is slightly more complex, but feasable. If the DBMS you use doesn&rsquo;t support <a href="https://vettabase.com/row-level-security-policy-in-postgresql/" data-type="post" data-id="38404">a specific way</a> to do this, you can still implement row-based permissions <a href="https://vettabase.com/mariadb-mysql-using-views-to-grant-or-deny-row-level-privileges/" data-type="post" data-id="38278">using stored procedures and views</a>.</p>
<h2 class="wp-block-heading">Configuration<a class="anchor-link" id="configuration"></a></h2>
<p class="wp-block-paragraph">If you&rsquo;re not using a database designed for analytical workloads (such as ClickHouse, Snowflake or Amazon Redshift) you should really configure it to optimise complex, long-running queries. We won&rsquo;t dig into the details, because they greatly depend on which database you use.</p>
<p class="wp-block-paragraph">Regardless which technology you use, it&rsquo;s important to make sure that:</p>
<ul class="wp-block-list">
<li>Your queries timeouts are long enough to permit analytical queries. I often insist that, for OLTP, granting all queries a timeout that is longer than 5 seconds is unreasonable. The user has already left the page, and the only effect of keeping the query running is consuming more database resources. But for analytical workloads, it would be utopic to assume than 10 minutes is a long time.</li>
<li>On the other hand, enough is enough. A query cannot run for days and make the database unresponsive or too slow for all other users. There should be a reasonable timeout. Some technologies or external tools allow to create more complex rules, and kill the queries that are using too many resources.</li>
</ul>
<h2 class="wp-block-heading">Database Design<a class="anchor-link" id="database-design"></a></h2>
<p class="wp-block-paragraph">Every table must express an entity or a relationship between entities, in a standard, clear, and clean way.</p>
<p class="wp-block-paragraph">When a table is a sparse matrix, the relationships between the objects it contains are unclear. When a table contains multiple entities, its meaning itself is unclear.</p>
<p class="wp-block-paragraph">Consider <a href="https://www.odoo-community.org/" rel="noopener">Odoo</a>&lsquo;s schema. Odoo is a most important open source CRM, and it&rsquo;s currently growing very fast in Europe. But &ndash; sorry to be blunt here &ndash; its PostgreSQL schema is poorly designed. Tables often have multiple meanings. Or they have one meaning for the developers, but this doesn&rsquo;t match the customers&rsquo; business objects and processes.</p>
<p class="wp-block-paragraph">For example, both customers and suppliers were stored in the <code>res_partner</code> table. In older versions, they could be distinguished thanks to boolean columns called <code>customer</code> and <code>supplier</code>. In newer versions, it got worse: to find customers we need to filter by <code>customer_rank &gt; 0</code>, and to find suppliers we need to filter by <code>supplier_rank &gt; 0</code>. While LLMs tend to know this, expect them to make many mistakes when you ask them to write complex queries that involve multiple unclear tables.</p>
<h2 class="wp-block-heading">Clear Naming<a class="anchor-link" id="clear-naming"></a></h2>
<p class="wp-block-paragraph">Unclear naming can easily confuse models. If a column tells you how many products of a certain type are in a certain warehouse, that column should be named <code>quantity</code>. It&rsquo;s also acceptable to call it <code>qty</code>, because it&rsquo;s a common abbreviation that models have encountered many times during their training. Calling it <code>count</code> can occasionally lead to confusion. Calling it <code>num</code> or <code>n</code> is a great way to get wrong results &ndash; and yes, I&rsquo;ve seen columns called <code>num</code> or <code>n</code>.</p>
<p class="wp-block-paragraph">The model needs to know the <em>type</em> of columns, too, so it can produce correct syntax ( <code>text_col='text' AND int_col=1</code>). If the typing is confusing, it can lead to errors. When naming is acceptable but not very clear, wrong typing can mislead models. For example, <code>dob</code> usually means Date of Birth, but it can have many other meanings. At least one of which relates to people too: Degree of Burglary. Using the proper type can avoid funny mistakes.</p>
<p class="wp-block-paragraph">See also my article <a href="https://vettabase.com/why-your-database-deserves-consistent-names-and-types/" data-type="post" data-id="366159">Why Your Database Deserves Consistent Names and Types</a>.</p>
<h2 class="wp-block-heading">Comments<a class="anchor-link" id="comments"></a></h2>
<p class="wp-block-paragraph">Tables, columns, views, and basically every schema object can have a comment. Use comments whenever the meaning of a column isn&rsquo;t obvious. Use it to clarify acronyms, to indicate the format of text columns, and above all to clarify obscure cases.</p>
<p class="wp-block-paragraph">Don&rsquo;t use comments when the meaning of something cannot realistically confuse the reader, avoiding added noise. Remember: humans and models need to focus their attention towards the right things to avoid making mistakes. Noise makes this difficult.</p>
<div id="awgt_1abfaecb7e8c" class="awgt-alert-content-wrap">#awgt_1abfaecb7e8c .awgt-alert-box {max-width: 100%;background: transparent;border-color: #007cba;}#awgt_1abfaecb7e8c .awgt-alert-content {color: #5a5a5a;padding: 15px 30px;}#awgt_1abfaecb7e8c .awgt-alert-content p {font-size: 16px;line-height: 24px;}#awgt_1abfaecb7e8c .awgt-alert-content a {color: #f44336;}#awgt_1abfaecb7e8c legend.awgt-alert-icon svg {fill: #007cba;width: 25px;height: 25px;}#awgt_1abfaecb7e8c legend.awgt-alert-icon {margin-left: 5%;padding: 0 12px;}#awgt_1abfaecb7e8c .awgt-alert-content.awgt-drop-cap p:first-child:first-letter {color: #000000;font-size: 40px;margin-top: 0px;}#awgt_1abfaecb7e8c .awgt-alert-content.awgt-first-bold &gt; p &gt; strong:first-child {color: #000000;padding-right: 5px;font-size: 16px;}#awgt_1abfaecb7e8c .awgt-alert-lay-3 .awgt-alert-content.awgt-first-bold &gt; p &gt; strong:first-child {display: block;padding-right: 0px !important;padding-bottom: 5px;}#awgt_1abfaecb7e8c fieldset.awgt-alert-box.awgt-alert-lay-3 legend.awgt-alert-icon:before {border-color: transparent transparent #007cba transparent;filter: brightness(0.7);}#awgt_1abfaecb7e8c fieldset.awgt-alert-box.awgt-alert-lay-3 legend.awgt-alert-icon:after {border: 26px solid #007cba;}
<fieldset class="awgt-alert-box awgt-lay-one">
<legend class="awgt-alert-icon"></legend>
<div class="awgt-alert-content">
<p>Also, schemas change over time. Make sure that comments change along with the schema, rather than becoming obsolete.</p>
</div>
</fieldset>
</div>
<h2 class="wp-block-heading">Views<a class="anchor-link" id="views"></a></h2>
<p class="wp-block-paragraph">Views don&rsquo;t eliminate complexity, they add up to existing complexity. The main undesirable consequences of this are that:</p>
<ol class="wp-block-list">
<li>The query planner / optimiser might ignore some indexes in the underlying tables, making your queries unnecessarily slow. In technical terms: views can prevent a predicate pushdown, forcing a view materialisation.</li>
<li>If models still learn about the underlying tables, they can get confused by multiple levels of complexity.</li>
</ol>
<p class="wp-block-paragraph">That said, if used wisely, views can make some queries simpler to create. If the model can avoid writing a JOIN involving many tables, filters and aggregations by using a single view, this will increase the chances that the rest of the query is correct and clean.</p>
<p class="wp-block-paragraph">You can also use views to <a href="https://vettabase.com/mariadb-mysql-using-views-to-grant-or-deny-row-level-privileges/" data-type="post" data-id="38278">hide sensitive information</a> from a model.</p>
<h2 class="wp-block-heading">Documentation and Data Dictionary<a class="anchor-link" id="documentation-and-data-dictionary"></a></h2>
<p class="wp-block-paragraph">Schemas need to be documented. Agents didn&rsquo;t eliminate this need, they made it more urgent.</p>
<p class="wp-block-paragraph">Suppose you ask &ldquo;which warehouses are critically full?&rdquo;. Maybe your schema has confusing naming, and warehouses are in a table called <code>store</code>. Maybe &ldquo;critically full&rdquo; is a precise concept used by your team, but its explanation can&rsquo;t be found in the data. Maybe it even has a different meaning for different teams (far from uncommon in medium/big companies).</p>
<p class="wp-block-paragraph">In the simplest cases, it&rsquo;s usually desirable to send some schema documentation to the models using the <em>system prompt</em> &ndash; the initial message that the agent sends to the model, followed by anything the user wrote. If you have dozens of tables, this is unlikely to be sufficient.</p>
<p class="wp-block-paragraph">For more complex cases, the system prompt should only include:</p>
<ul class="wp-block-list">
<li>A generic explanation of the business domain and the schema.</li>
<li>A dictionary of the business terms that might be found in the user&rsquo;s request. If necessary, this dictionary should vary from team to team.</li>
<li>Documentation of naming rules that are used consistently across the schema.</li>
<li>Documentation of the most common tables &ndash; the ones that have a good chance of being referenced in a query.</li>
<li>A list of entity groups &ndash; for example: customer entities, inventory entities, payment entities, etc. Entities are tables and views. Some entities might be part of multiple groups.</li>
</ul>
<p class="wp-block-paragraph">With this information, the model should understand:</p>
<ul class="wp-block-list">
<li>How to compose very common queries.</li>
<li>Which groups it needs more information about to fulfill a user request.</li>
</ul>
<p class="wp-block-paragraph">The model will be instructed to let the agent know, in a machine-readable format, if it needs informaiton about some entity groups, and which ones. When this happens, the model will be provided with additional documentation. In the AI jargon, these pieces of additional documentation are called resources.</p>
<div id="awgt_32adfa4ad343" class="awgt-alert-content-wrap">#awgt_32adfa4ad343 .awgt-alert-box {max-width: 100%;background: transparent;border-color: #007cba;}#awgt_32adfa4ad343 .awgt-alert-content {color: #5a5a5a;padding: 15px 30px;}#awgt_32adfa4ad343 .awgt-alert-content p {font-size: 16px;line-height: 24px;}#awgt_32adfa4ad343 .awgt-alert-content a {color: #f44336;}#awgt_32adfa4ad343 legend.awgt-alert-icon svg {fill: #007cba;width: 25px;height: 25px;}#awgt_32adfa4ad343 legend.awgt-alert-icon {margin-left: 5%;padding: 0 12px;}#awgt_32adfa4ad343 .awgt-alert-content.awgt-drop-cap p:first-child:first-letter {color: #000000;font-size: 40px;margin-top: 0px;}#awgt_32adfa4ad343 .awgt-alert-content.awgt-first-bold &gt; p &gt; strong:first-child {color: #000000;padding-right: 5px;font-size: 16px;}#awgt_32adfa4ad343 .awgt-alert-lay-3 .awgt-alert-content.awgt-first-bold &gt; p &gt; strong:first-child {display: block;padding-right: 0px !important;padding-bottom: 5px;}#awgt_32adfa4ad343 fieldset.awgt-alert-box.awgt-alert-lay-3 legend.awgt-alert-icon:before {border-color: transparent transparent #007cba transparent;filter: brightness(0.7);}#awgt_32adfa4ad343 fieldset.awgt-alert-box.awgt-alert-lay-3 legend.awgt-alert-icon:after {border: 26px solid #007cba;}
<fieldset class="awgt-alert-box awgt-lay-one">
<legend class="awgt-alert-icon"></legend>
<div class="awgt-alert-content">
<p>Remember that models, just like humans, understand examples and patterns better than long explanations. For example, it&rsquo;s better to omit an explanation and tell the model that a string matches this format: &lt;project id-or-label=&rdquo;?&rdquo;&gt;&lt;customer id=&rdquo;?&rdquo;&gt;&lt;ticket title=&rdquo;?&rdquo;/&gt;&lt;/customer&gt;&lt;/project&gt;</p>
</div>
</fieldset>
</div>
<h2 class="wp-block-heading">SQL Advanced Features and Dialects<a class="anchor-link" id="sql-advanced-features-and-dialects"></a></h2>
<p class="wp-block-paragraph">SQL has many advanced features that are incredibly powerful, but not widely used. To make things worse, every dialect has non-standard variations that normally work on one DBMS only. This often confuses models. I&rsquo;ve seen cases where they suggest using MariaDB temporal tables features on PostgreSQL, or where they use PostgreSQL type conversion syntax in a query for MySQL.</p>
<p class="wp-block-paragraph">It is a good idea to use a set of <a href="http://agentskills.io/" rel="noopener">agent skills</a> or <a href="https://modelcontextprotocol.io/specification/2025-06-18/server/resources" rel="noopener">MCP resources</a> to inform the model about the syntaxes that are supported by the DBMS you use. For example, MariaDB Foundation maintains a set of <a href="https://github.com/MariaDB/skills" rel="noopener">MariaDB skills</a> that you can easily make available to your agents.</p>
<h2 class="wp-block-heading">Semantic Layers<a class="anchor-link" id="semantic-layers"></a></h2>
<p class="wp-block-paragraph">There is a movement in the data industry toward Semantic Layers (like dbt Semantic Layer, Cube, LookML). You don&rsquo;t have to necessarily adopt one of them, as you can build your semantics in a more reliable, cheaper and cleaner way by following the good practices listed here. However, I want to mention that semantic layers exist for the sake of completeness.</p>
<h2 class="wp-block-heading">Training<a class="anchor-link" id="training"></a></h2>
<p class="wp-block-paragraph">This is the last link of the chain, but make no mistake: you want to make sure it&rsquo;s not the weakest! A technology can&rsquo;t save humans from using it in a bad way.</p>
<p class="wp-block-paragraph">It&rsquo;s important to train AI users. They need to know:</p>
<ul class="wp-block-list">
<li>How to formulate a good question;</li>
<li>How to make sure the model understands a question;</li>
<li>How to make sure the model has enough information to emit an informed answer;</li>
<li>How to make sure the model has made a correct reasoning;</li>
<li>How to spot hallucinations.</li>
</ul>
<p class="wp-block-paragraph">This is important to avoid the trap of AI&rsquo;s hallucinations.</p>
<p class="wp-block-paragraph">AI training is not part of our services, but we can direct you to reliable partners.</p>
<h2 class="wp-block-heading">Conclusions<a class="anchor-link" id="conclusions"></a></h2>
<p class="wp-block-paragraph">Many schemas are far from having a clean, self-documenting structure. You need to make sure that models understand user requests and translate them into correct SQL by fixing the schema where needed, and by adding relevant documentation. Changing the schema is better in terms of correctness of the results and token saving, but the required effort is not always reasonable. With clear, precise, well-structured documentation it is still possible to obtain good SQL queries from a model.</p>
<p class="wp-block-paragraph">We can help you prepare your schemas for agents. <a href="https://vettabase.com/contact/" data-type="page" data-id="11">Contact us</a> to discuss what we can do.</p>
<p class="wp-block-paragraph"><em>Federico Razzoli</em></p>
<p class="wp-block-paragraph">
</p><p class="wp-block-paragraph">
</p>
<p><a href="https://vettabase.com/preparing-your-analytical-databases-for-agents/">Preparing Your Analytical Databases for Agents</a> appeared first on <a 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><a 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 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><a 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 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><a 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 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><a 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 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><a 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 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><a 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 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><a href="https://mariadb.org/passbolt-renews-its-support-for-mariadb-foundation/">Passbolt renews its support for MariaDB Foundation</a> appeared first on <a 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><a href="https://mariadb.org/passbolt-renews-its-support-for-mariadb-foundation/">Passbolt renews its support for MariaDB Foundation</a> appeared first on <a 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><a 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 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><a 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 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><a 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 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><a 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 href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Do not uselessly grant CREATE and ALTER TABLE</title>
      <link rel="alternate" type="text/html" href="https://jfg-mysql.blogspot.com/2026/06/do-not-uselessly-grant-create-and-alter-table.html" />
      <id>https://jfg-mysql.blogspot.com/2026/06/do-not-uselessly-grant-create-and-alter-table.html</id>
      <updated>2026-06-20T22:52:52+00:00</updated>
      <author><name>Jean-François Gagné</name></author>
      <summary type="html"><![CDATA[<p>This lesson should have been learned with the CREATE TABLE of death, but it is worth a refresh.</p>
<p>Do not uselessly grant CREATE and ALTER TABLE</p>
<p>The reason I am posting this reminder is that another crashing bug related to DDL came to my attention.&#160; This bug is only fixed in a recent version of MySQL (probably not affecting 5.6 and 5.7), so if you are running the latest 8.0 or 8.4, you should</p>
<p><a href="https://jfg-mysql.blogspot.com/2026/06/do-not-uselessly-grant-create-and-alter-table.html">Do not uselessly grant CREATE and ALTER TABLE</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>This lesson should have been learned with the CREATE TABLE of death, but it is worth a refresh.</p>
<p>Do not uselessly grant CREATE and ALTER TABLE</p>
<p>The reason I am posting this reminder is that another crashing bug related to DDL came to my attention.&amp;nbsp; This bug is only fixed in a recent version of MySQL (probably not affecting 5.6 and 5.7), so if you are running the latest 8.0 or 8.4, you should</p>

<p><a href="https://jfg-mysql.blogspot.com/2026/06/do-not-uselessly-grant-create-and-alter-table.html">Do not uselessly grant CREATE and ALTER TABLE</a> appeared first on <a 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><a href="https://mariadb.org/mariadb-13-1-preview-available/">MariaDB 13.1 preview available</a> appeared first on <a 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><a href="https://mariadb.org/mariadb-13-1-preview-available/">MariaDB 13.1 preview available</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>CPU-bound sysbench on a large server: Postgres 12 to 19 beta1</title>
      <link rel="alternate" type="text/html" href="https://smalldatum.blogspot.com/2026/06/cpu-bound-sysbench-on-large-server.html" />
      <id>https://smalldatum.blogspot.com/2026/06/cpu-bound-sysbench-on-large-server.html</id>
      <updated>2026-06-20T00:18:58+00:00</updated>
      <author><name>Mark Callaghan</name></author>
      <summary type="html"><![CDATA[<p>This has results from sysbench on a small server with Postgres versions 12 through 19 beta1. Sysbench is run with high concurrency (40 connections) and a cached database. The purpose is to search for changes in performance.Postgres remains boring, it is hard to find performance regressions.tl;dr for Postgres 17 to 19there are no regressionsthroughput on the read-only-count test improves by ~3X in 19 beta1 thanks to a better query plantl;dr for Postgres 12 to 19there are few regressions, throughput might have dropped by up to 5% on a few range query teststhere are a few large improvements for read-only teststhere are many large improvements for write-heavy testsBuilds, configuration and hardwareI compiled Postgres from source for versions 12.22, 13.23, 14.23, 15.18, 16.14, 17.10, 18.4 and 19 beta1.I used a 48-core server from Hetzneran ax162s with an AMD EPYC 9454P 48-Core Processor with SMT disabled2 Intel D7-P5520 NVMe storage devices with RAID 1 (3.8T each) using ext4128G RAMUbuntu 24.04Configuration files for Postgres:the config file is named conf.diff.cx10a_c32r128 (x10a_c32r128) and is here for versions 12, 13, 14, 15, 16 and 17.for Postgres 18 and 19 I used conf.diff.cx10b_c32r128 (x10b_c32r128) which is as close as possible to the Postgres 17 config and uses io_method=syncBenchmarkI used sysbench and my usage is explained here. I now run 32 of the 42 microbenchmarks listed in that blog post. Most test only one type of SQL statement. Benchmarks are run with the database cached by Postgres.The read-heavy microbenchmarks are run for 600 seconds and the write-heavy for 1200 seconds. The benchmark is run with 40 clients and 8 tables with 10M rows per table. The database is cached.The purpose is to search for regressions from new CPU overhead and mutex contention. I use the small server with low concurrency to find regressions from new CPU overheads and then larger servers with high concurrency to find regressions from new CPU overheads and mutex contention.The tests can be called microbenchmarks. They are very synthetic. But microbenchmarks also make it easy to understand which types of SQL statements have great or lousy performance. Performance testing benefits from a variety of workloads -- both more and less synthetic.ResultsThe microbenchmarks are split into 4 groups -- 1 for point queries, 2 for range queries, 1 for writes. For the range query microbenchmarks, part 1 has queries that don\'t do aggregation while part 2 has queries that do aggregation. I provide charts below with relative QPS (rQPS). The relative QPS is the following:(QPS for some version) / (QPS for base version)When the relative QPS is > 1 then some version is faster than base version.  When it is < 1 then there might be a regression. Values from iostat and vmstat divided by QPS are also provided here. These can help to explain why something is faster or slower because it shows how much HW is used per request.Here, base version is either Postgres 12.23 or 17.10 and some version is a more recent version. I use 12.23 as the base version to identify regressions over a long period of time. And then I use 17.10 as the base version to confirm there aren\'t recent, large regressions.I describe performance changes (changes to relative QPS) in terms of basis points. Performance changes by one basis point when the difference in rQPS is 0.01. When rQPS decreases from 0.95 to 0.85 then it changed by 10 basis points.Results: point queries, version 17 to 19Summary:there are no regressinsRelative to: PG 17.10col-1 : PG 18.4col-2 : PG 19 beta1col-1   col-21.00    0.99    hot-points1.01    1.01    point-query1.00    1.00    points-covered-pk0.98    0.99    points-covered-si1.01    1.00    points-notcovered-pk1.00    1.00    points-notcovered-si1.01    1.01    random-points_range=101.02    1.00    random-points_range=1001.00    1.00    random-points_range=1000Results: point queries, version 12 to 19Summarythere are no regressionsthroughput for the hot-points test improves by ~2X in versions 17.10, 18.4 and 19betaRelative to: PG 12.22col-1 : PG 13.23col-2 : PG 14.23col-3 : PG 15.18col-4 : PG 16.14col-5 : PG 17.10col-6 : PG 18.4col-7 : PG 19 beta1col-1   col-2   col-3   col-4   col-5   col-6   col-71.00    0.90    0.97    1.03    2.34    2.35    2.31    hot-points1.00    1.01    1.03    1.04    1.03    1.04    1.03    point-query1.02    1.04    1.04    1.07    1.04    1.04    1.04    points-covered-pk1.01    1.07    1.04    1.04    1.04    1.03    1.04    points-covered-si0.98    1.01    1.03    1.02    1.00    1.01    1.00    points-notcovered-pk0.99    1.03    1.03    1.01    1.02    1.02    1.01    points-notcovered-si0.99    1.01    1.03    1.03    1.00    1.01    1.01    random-points_range=100.99    1.02    1.04    1.04    1.01    1.03    1.01    random-points_range=1001.00    1.02    1.02    1.03    1.01    1.02    1.01    random-points_range=1000Results: range queries without aggregation, version 17 to 19Summarythere are no regressionswhile 19 beta1 has a better result on the scan test, that test has more variance with Postgres so I am reluctant to judge this without more resultsRelative to: PG 17.10col-1 : PG 18.4col-2 : PG 19 beta1col-1   col-20.98    0.99    range-covered-pk0.97    0.99    range-covered-si0.99    0.99    range-notcovered-pk1.02    1.01    range-notcovered-si0.96    1.07    scanResults: range queries without aggregation, version 12 to 19Summarythere are no regressionsscan throughput has improved a lot from version 12 to 19Relative to: PG 12.22col-1 : PG 13.23col-2 : PG 14.23col-3 : PG 15.18col-4 : PG 16.14col-5 : PG 17.10col-6 : PG 18.4col-7 : PG 19 beta1col-1   col-2   col-3   col-4   col-5   col-6   col-70.99    1.03    1.04    1.04    1.03    1.00    1.02    range-covered-pk0.99    1.04    1.04    1.04    1.03    1.00    1.03    range-covered-si1.00    1.00    1.00    0.99    1.00    0.99    0.99    range-notcovered-pk1.00    1.01    1.01    0.99    1.00    1.02    1.01    range-notcovered-si1.09    1.27    1.10    1.21    1.19    1.14    1.28    scanResults: range queries with aggregation, version 17 to 19Summarythere are no regressionsthroughput on the read-only-count test is ~3X better thanks to a new query plan. This improvement was also visible on my small serverRelative to: PG 17.10col-1 : PG 18.4col-2 : PG 19 beta1col-1   col-21.03    3.30    read-only-count1.02    0.99    read-only-distinct1.00    0.97    read-only-order0.99    0.99    read-only_range=100.99    0.99    read-only_range=1001.01    1.00    read-only_range=100001.03    1.01    read-only-simple1.03    1.01    read-only-sumResults: range queries with aggregation, version 12 to 19Summarythere might be a few small regressions, but losing 5% throughput from version 12 to 19 isn\'t a big dealthroughput on the read-only-count test is ~3X better thanks to a new query plan. This improvement was also visible on my small serverRelative to: PG 12.22col-1 : PG 13.23col-2 : PG 14.23col-3 : PG 15.18col-4 : PG 16.14col-5 : PG 17.10col-6 : PG 18.4col-7 : PG 19 beta1col-1   col-2   col-3   col-4   col-5   col-6   col-71.01    0.95    0.96    0.97    0.93    0.95    3.06    read-only-count1.00    0.98    0.98    0.98    0.96    0.98    0.95    read-only-distinct1.00    0.98    0.98    1.00    0.99    0.99    0.97    read-only-order0.99    1.00    1.01    1.00    1.01    0.99    1.00    read-only_range=100.99    1.00    1.00    1.00    1.01    1.00    0.99    read-only_range=1001.00    0.97    1.02    1.03    1.04    1.05    1.03    read-only_range=100001.00    0.97    0.99    0.97    0.95    0.98    0.96    read-only-simple1.00    0.96    0.97    0.97    0.94    0.97    0.95    read-only-sumResults: writes, version 17 to 19Summarythere are no regressionsRelative to: PG 17.10col-1 : PG 18.4col-2 : PG 19 beta1col-1   col-20.99    0.99    delete1.02    1.02    insert1.00    0.98    read-write_range=100.99    0.99    read-write_range=1001.01    1.03    update-index1.01    0.98    update-inlist0.98    1.01    update-nonindex1.01    1.03    update-one1.00    1.00    update-zipf0.97    0.99    write-onlyResults: writes, version 12 to 19Summarythere are no regressionsmany large improvements arrived in version 17 and remain in 19 beta1Relative to: PG 12.22col-1 : PG 13.23col-2 : PG 14.23col-3 : PG 15.18col-4 : PG 16.14col-5 : PG 17.10col-6 : PG 18.4col-7 : PG 19 beta1col-1   col-2   col-3   col-4   col-5   col-6   col-70.99    1.11    1.13    1.10    1.28    1.27    1.27    delete1.02    1.17    1.16    1.19    1.23    1.25    1.25    insert1.00    1.20    1.22    1.20    1.24    1.24    1.22    read-write_range=100.99    1.04    1.05    1.04    1.06    1.05    1.04    read-write_range=1000.98    1.08    1.05    0.94    1.84    1.85    1.90    update-index1.00    1.07    1.06    1.05    1.12    1.13    1.10    update-inlist1.01    1.07    1.07    0.86    1.87    1.84    1.88    update-nonindex1.04    0.96    0.96    1.10    1.39    1.41    1.43    update-one1.01    1.05    1.07    0.96    1.63    1.62    1.63    update-zipf0.99    1.11    1.13    1.09    1.41    1.37    1.40    write-only
</p>
<p><a href="https://smalldatum.blogspot.com/2026/06/cpu-bound-sysbench-on-large-server.html">CPU-bound sysbench on a large server: Postgres 12 to 19 beta1</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>This has results from sysbench on a small server with Postgres versions 12 through 19 beta1. Sysbench is run with high concurrency (40 connections) and a cached database. The purpose is to search for changes in performance.</p>
<p>Postgres remains boring, it is hard to find performance regressions.</p>
<p>tl;dr for Postgres 17 to 19</p>

<ul style="text-align: left">
<li>there are no regressions</li>
<li>throughput on the read-only-count test improves by ~3X in 19 beta1 thanks to a better query plan</li>
</ul>
<div>
<p>tl;dr for Postgres 12 to 19</p>

<ul>
<li>there are few regressions, throughput might have dropped by up to 5% on a few range query tests</li>
<li>there are a few large improvements for read-only tests</li>
<li>there are many large improvements for write-heavy tests</li>
</ul>
</div>
<div>
<div>
<div>
<div style="background-color: white"><b>Builds, configuration and hardware</b></div>
<div style="background-color: white">
<div>I compiled Postgres from source for versions 12.22, 13.23, 14.23, 15.18, 16.14, 17.10, 18.4 and 19 beta1.</div>
<div></div>
<div><span style="font-family: inherit">I used a 48-core server from Hetzner</span></div>
<div>
<ul>
<li>an ax162s with an AMD EPYC 9454P 48-Core Processor with SMT disabled</li>
<li>2 Intel D7-P5520 NVMe storage devices with RAID 1 (3.8T each) using ext4</li>
<li>128G RAM</li>
<li>Ubuntu 24.04</li>
</ul>
<div>
<div><span style="font-family: inherit">Configuration files for Postgres:</span></div>
<div>
<ul>
<li><span style="font-family: inherit">the config file is named conf.diff.cx10a_c32r128 (x10a_c32r128) and is here for versions </span><a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg1219_o2nofp/conf.diff.cx10a_c32r128">12</a>, <a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg1315_o2nofp/conf.diff.cx10a_c32r128">13</a>, <a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg1412_o2nofp/conf.diff.cx10a_c32r128">14</a>, <a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg157_o2nofp/conf.diff.cx10a_c32r128">15</a>, <a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg163_o2nofp/conf.diff.cx10a_c32r128">16</a> and <a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg17beta1_o2nofp/conf.diff.cx10a_c32r128">17</a>.</li>
<li>for Postgres 18 and 19 I used <a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg18beta3_o2nofp/conf.diff.cx10b_c32r128" style="font-family: inherit">conf.diff.cx10b_c32r128</a><span style="font-family: inherit"> </span><span style="font-family: inherit">(x10b_c32r128) which is as close as possible to the Postgres 17 config and </span>uses io_method=sync</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div><b>Benchmark</b></div>
<div>
<div></div>
<div>I used sysbench and my usage is <a href="http://smalldatum.blogspot.com/2017/02/using-modern-sysbench-to-compare.html">explained here</a>. I now run 32 of the 42 microbenchmarks listed in that blog post. Most test only one type of SQL statement. Benchmarks are run with the database cached by Postgres.</div>
<div>The read-heavy microbenchmarks are run for 600 seconds and the write-heavy for 1200 seconds. The benchmark is run with 40 clients and 8 tables with 10M rows per table. The database is cached.</div>
</div>
</div>
<div></div>
<div>The purpose is to search for regressions from new CPU overhead and mutex contention. I use the small server with low concurrency to find regressions from new CPU overheads and then larger servers with high concurrency to find regressions from new CPU overheads and mutex contention.</div>
</div>
<div>
<div></div>
<div>The tests can be called microbenchmarks. They are very synthetic. But microbenchmarks also make it easy to understand which types of SQL statements have great or lousy performance. Performance testing benefits from a variety of workloads &mdash; both more and less synthetic.</div>
<div></div>
<div>
<div style="font-family: inherit"><b>Results</b></div>
<div><span>
<div style="font-family: inherit"></div>
<div style="font-family: inherit"><span style="font-family: inherit">The microbenchmarks are split into 4 groups &mdash; 1 for point queries, 2 for range queries, 1 for writes. For the range query microbenchmarks, part 1 has queries that don&rsquo;t do aggregation while part 2 has queries that do aggregation.&nbsp;</span></div>
<div style="font-family: inherit">I provide charts below with relative QPS (rQPS). The relative QPS is the following:</div>
<div style="font-family: inherit">
<div></div>
<blockquote><p>(QPS for some version) / (QPS for base version)</p></blockquote>
</div>
<div><span style="font-family: inherit">When the relative QPS is &gt; 1 then </span><i style="font-family: inherit">some version</i><span style="font-family: inherit"> is faster than </span><i style="font-family: inherit">base version</i><span style="font-family: inherit">.&nbsp; When it is &lt; 1 then there might be a regression. </span><span><span style="font-family: inherit">Values from iostat and vmstat divided by QPS are also </span><a href="https://github.com/mdcallag/mytools/blob/master/bench/arc/aug25.sb.mem.in.1u.50m.600s.900s.pn53/o.met.all" style="font-family: inherit">provided here</a><span style="font-family: inherit">. These can help to explain why something is faster or slower because it shows how much HW is used per request.</span></span></div>
<div><span><br></span></div>
<div><span>Here, <i>base version</i> is either Postgres 12.23 or 17.10 and <i>some version</i> is a more recent version. I use 12.23 as the base version to identify regressions over a long period of time. And then I use 17.10 as the base version to confirm there aren&rsquo;t recent, large regressions.
<p><span>I describe performance changes (changes to relative QPS) in terms of basis points. Performance changes by one </span><i style="font-family: inherit"><b>basis point</b></i><span> when the difference in rQPS is 0.01. When rQPS decreases from 0.95 to 0.85 then it changed by 10 basis points.</span></p></span></div>
<div><span><span><br></span></span></div>
<div><b>Results: point queries, version 17 to 19</b></div>
<div><span><span><br></span></span></div>
<div><span><span>Summary:</span></span></div>
<div>
<ul style="text-align: left">
<li><span><span>there are no regressins</span></span></li>
</ul>
</div>
<div><span style="font-family: courier;font-size: small">Relative to: PG 17.10</span></div>
<div><span><span style="font-family: courier;font-size: x-small">
<div>col-1 : PG 18.4</div>
<div>col-2 : PG 19 beta1</div>
<div></div>
<div>
<div>col-1&nbsp; &nbsp;col-2</div>
<div>1.00&nbsp; &nbsp; 0.99&nbsp; &nbsp; hot-points</div>
<div>1.01&nbsp; &nbsp; 1.01&nbsp; &nbsp; point-query</div>
<div>1.00&nbsp; &nbsp; 1.00&nbsp; &nbsp; points-covered-pk</div>
<div>0.98&nbsp; &nbsp; 0.99&nbsp; &nbsp; points-covered-si</div>
<div>1.01&nbsp; &nbsp; 1.00&nbsp; &nbsp; points-notcovered-pk</div>
<div>1.00&nbsp; &nbsp; 1.00&nbsp; &nbsp; points-notcovered-si</div>
<div>1.01&nbsp; &nbsp; 1.01&nbsp; &nbsp; random-points_range=10</div>
<div>1.02&nbsp; &nbsp; 1.00&nbsp; &nbsp; random-points_range=100</div>
<div>1.00&nbsp; &nbsp; 1.00&nbsp; &nbsp; random-points_range=1000</div>
</div>
<p></p></span></span></div>
<div><span><span><br></span></span></div>
<div><span><span><b>Results: point queries, version 12 to 19</b></span></span></div>
<div><span><span><b><br></b></span></span></div>
<div>Summary</div>
<div>
<ul style="text-align: left">
<li>there are no regressions</li>
<li>throughput for the hot-points test improves by ~2X in versions 17.10, 18.4 and 19beta</li>
</ul>
</div>
<div><span style="font-family: courier;font-size: small">Relative to: PG 12.22</span></div>
<div><span><span style="font-family: courier;font-size: x-small">
<div>col-1 : PG 13.23</div>
<div>col-2 : PG 14.23</div>
<div>col-3 : PG 15.18</div>
<div>col-4 : PG 16.14</div>
<div>col-5 : PG 17.10</div>
<div>col-6 : PG 18.4</div>
<div>col-7 : PG 19 beta1</div>
<p></p></span></span></div>
<div><span><span style="font-family: courier;font-size: x-small"><br></span></span></div>
<div><span><span>
<div><span style="font-family: courier;font-size: x-small">col-1&nbsp; &nbsp;col-2&nbsp; &nbsp;col-3&nbsp; &nbsp;col-4&nbsp; &nbsp;col-5&nbsp; &nbsp;col-6&nbsp; &nbsp;col-7</span></div>
<div><span style="font-family: courier;font-size: x-small">1.00&nbsp; &nbsp; 0.90&nbsp; &nbsp; 0.97&nbsp; &nbsp; 1.03&nbsp; &nbsp; <span style="background-color: #d9ead3">2.34</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">2.35</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">2.31</span>&nbsp; &nbsp; hot-points</span></div>
<div><span style="font-family: courier;font-size: x-small">1.00&nbsp; &nbsp; 1.01&nbsp; &nbsp; 1.03&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.03&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.03&nbsp; &nbsp; point-query</span></div>
<div><span style="font-family: courier;font-size: x-small">1.02&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.07&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.04&nbsp; &nbsp; points-covered-pk</span></div>
<div><span style="font-family: courier;font-size: x-small">1.01&nbsp; &nbsp; 1.07&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.03&nbsp; &nbsp; 1.04&nbsp; &nbsp; points-covered-si</span></div>
<div><span style="font-family: courier;font-size: x-small">0.98&nbsp; &nbsp; 1.01&nbsp; &nbsp; 1.03&nbsp; &nbsp; 1.02&nbsp; &nbsp; 1.00&nbsp; &nbsp; 1.01&nbsp; &nbsp; 1.00&nbsp; &nbsp; points-notcovered-pk</span></div>
<div><span style="font-family: courier;font-size: x-small">0.99&nbsp; &nbsp; 1.03&nbsp; &nbsp; 1.03&nbsp; &nbsp; 1.01&nbsp; &nbsp; 1.02&nbsp; &nbsp; 1.02&nbsp; &nbsp; 1.01&nbsp; &nbsp; points-notcovered-si</span></div>
<div><span style="font-family: courier;font-size: x-small">0.99&nbsp; &nbsp; 1.01&nbsp; &nbsp; 1.03&nbsp; &nbsp; 1.03&nbsp; &nbsp; 1.00&nbsp; &nbsp; 1.01&nbsp; &nbsp; 1.01&nbsp; &nbsp; random-points_range=10</span></div>
<div><span style="font-family: courier;font-size: x-small">0.99&nbsp; &nbsp; 1.02&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.01&nbsp; &nbsp; 1.03&nbsp; &nbsp; 1.01&nbsp; &nbsp; random-points_range=100</span></div>
<div><span style="font-family: courier;font-size: x-small">1.00&nbsp; &nbsp; 1.02&nbsp; &nbsp; 1.02&nbsp; &nbsp; 1.03&nbsp; &nbsp; 1.01&nbsp; &nbsp; 1.02&nbsp; &nbsp; 1.01&nbsp; &nbsp; random-points_range=1000</span></div>
<p></p></span></span></div>
<p></p></span></div>
</div>
<div><br style="background-color: white"></div>
</div>
<div>
<div><span><b>Results: range queries without aggregation, version 17 to 19</b></span></div>
<div><span><br></span></div>
<div>
<div>Summary</div>
<div>
<ul>
<li>there are no regressions</li>
<li>while 19 beta1 has a better result on the scan test, that test has more variance with Postgres so I am reluctant to judge this without more results</li>
</ul>
</div>
</div>
</div>
<div><span style="font-family: courier;font-size: small">Relative to: PG 17.10</span></div>
<div><span style="font-family: courier;font-size: x-small">
<div>col-1 : PG 18.4</div>
<div>col-2 : PG 19 beta1</div>
<p></p></span></div>
<div><span style="font-family: courier;font-size: x-small"><br></span></div>
<div><span>
<div>
<div><span style="font-family: courier;font-size: x-small">col-1&nbsp; &nbsp;col-2</span></div>
<div><span style="font-family: courier;font-size: x-small">0.98&nbsp; &nbsp; 0.99&nbsp; &nbsp; range-covered-pk</span></div>
<div><span style="font-family: courier;font-size: x-small">0.97&nbsp; &nbsp; 0.99&nbsp; &nbsp; range-covered-si</span></div>
<div><span style="font-family: courier;font-size: x-small">0.99&nbsp; &nbsp; 0.99&nbsp; &nbsp; range-notcovered-pk</span></div>
<div><span style="font-family: courier;font-size: x-small">1.02&nbsp; &nbsp; 1.01&nbsp; &nbsp; range-notcovered-si</span></div>
<div><span style="font-family: courier;font-size: x-small">0.96&nbsp; &nbsp; <span style="background-color: #d9ead3">1.07</span>&nbsp; &nbsp; scan</span></div>
</div>
<p></p></span></div>
<div><span>
<div><span><span><br></span></span></div>
<div><span><span><b>Results: range queries without aggregation, version 12 to 19</b></span></span></div>
<div><span><span><br></span></span></div>
<div>Summary</div>
<div>
<ul style="text-align: left">
<li>there are no regressions</li>
<li>scan throughput has improved a lot from version 12 to 19</li>
</ul>
</div>
<div><span style="font-family: courier;font-size: small">Relative to: PG 12.22</span></div>
<div><span><span style="font-family: courier;font-size: x-small">
<div>col-1 : PG 13.23</div>
<div>col-2 : PG 14.23</div>
<div>col-3 : PG 15.18</div>
<div>col-4 : PG 16.14</div>
<div>col-5 : PG 17.10</div>
<div>col-6 : PG 18.4</div>
<div>col-7 : PG 19 beta1</div>
<div></div>
<p></p></span></span></div>
<div><span><span style="font-family: courier;font-size: x-small">
<div>col-1&nbsp; &nbsp;col-2&nbsp; &nbsp;col-3&nbsp; &nbsp;col-4&nbsp; &nbsp;col-5&nbsp; &nbsp;col-6&nbsp; &nbsp;col-7</div>
<div>0.99&nbsp; &nbsp; 1.03&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.03&nbsp; &nbsp; 1.00&nbsp; &nbsp; 1.02&nbsp; &nbsp; range-covered-pk</div>
<div>0.99&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.03&nbsp; &nbsp; 1.00&nbsp; &nbsp; 1.03&nbsp; &nbsp; range-covered-si</div>
<div>1.00&nbsp; &nbsp; 1.00&nbsp; &nbsp; 1.00&nbsp; &nbsp; 0.99&nbsp; &nbsp; 1.00&nbsp; &nbsp; 0.99&nbsp; &nbsp; 0.99&nbsp; &nbsp; range-notcovered-pk</div>
<div>1.00&nbsp; &nbsp; 1.01&nbsp; &nbsp; 1.01&nbsp; &nbsp; 0.99&nbsp; &nbsp; 1.00&nbsp; &nbsp; 1.02&nbsp; &nbsp; 1.01&nbsp; &nbsp; range-notcovered-si</div>
<div><span style="background-color: #d9ead3">1.09</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.27</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.10</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.21</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.19</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.14</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.28</span>&nbsp; &nbsp; scan</div>
<p></p></span></span></div>
<p></p></span></div>
<div><span>
<div></div>
<div><span><span><b>Results: range queries with aggregation, version 17 to 19</b></span></span></div>
<div><span><span><br></span></span></div>
<div>Summary</div>
<div>
<ul style="text-align: left">
<li>there are no regressions</li>
<li>throughput on the read-only-count test is ~3X better thanks to a new query plan. This improvement <a href="https://smalldatum.blogspot.com/2026/06/postgres-19-beta1-vs-sysbench-on-small.html">was also visible</a> on my small server</li>
</ul>
</div>
<div><span style="font-family: courier;font-size: small">Relative to: PG 17.10</span></div>
<div><span><span style="font-family: courier;font-size: x-small">
<div>col-1 : PG 18.4</div>
<div>col-2 : PG 19 beta1</div>
<div></div>
<div>
<div>col-1&nbsp; &nbsp;col-2</div>
<div>1.03&nbsp; &nbsp; <span style="background-color: #d9ead3">3.30</span>&nbsp; &nbsp; read-only-count</div>
<div>1.02&nbsp; &nbsp; 0.99&nbsp; &nbsp; read-only-distinct</div>
<div>1.00&nbsp; &nbsp; 0.97&nbsp; &nbsp; read-only-order</div>
<div>0.99&nbsp; &nbsp; 0.99&nbsp; &nbsp; read-only_range=10</div>
<div>0.99&nbsp; &nbsp; 0.99&nbsp; &nbsp; read-only_range=100</div>
<div>1.01&nbsp; &nbsp; 1.00&nbsp; &nbsp; read-only_range=10000</div>
<div>1.03&nbsp; &nbsp; 1.01&nbsp; &nbsp; read-only-simple</div>
<div>1.03&nbsp; &nbsp; 1.01&nbsp; &nbsp; read-only-sum</div>
</div>
<p></p></span></span></div>
<div><span><span>
<div><span><span><br></span></span></div>
<div><span><span><b>Results: range queries with aggregation, version 12 to 19</b></span></span></div>
<div><span><span><br></span></span></div>
<div><span><span>
<div>Summary</div>
<div>
<ul style="text-align: left">
<li>there might be a few small regressions, but losing 5% throughput from version 12 to 19 isn&rsquo;t a big deal</li>
<li>throughput on the read-only-count test is ~3X better thanks to a new query plan. This improvement <a href="https://smalldatum.blogspot.com/2026/06/postgres-19-beta1-vs-sysbench-on-small.html">was also visible</a> on my small server</li>
</ul>
</div>
<div><span style="font-family: courier;font-size: small">Relative to: PG 12.22</span></div>
<div><span style="font-family: courier;font-size: x-small">col-1 : PG 13.23</span></div>
<div><span style="font-family: courier;font-size: x-small">col-2 : PG 14.23</span></div>
<div><span style="font-family: courier;font-size: x-small">col-3 : PG 15.18</span></div>
<div><span style="font-family: courier;font-size: x-small">col-4 : PG 16.14</span></div>
<div><span style="font-family: courier;font-size: x-small">col-5 : PG 17.10</span></div>
<div><span style="font-family: courier;font-size: x-small">col-6 : PG 18.4</span></div>
<div><span style="font-family: courier;font-size: x-small">col-7 : PG 19 beta1</span></div>
<div><span style="font-family: courier;font-size: x-small"><br></span></div>
<p></p></span></span></div>
<div><span><span style="font-family: courier;font-size: x-small">
<div>col-1&nbsp; &nbsp;col-2&nbsp; &nbsp;col-3&nbsp; &nbsp;col-4&nbsp; &nbsp;col-5&nbsp; &nbsp;col-6&nbsp; &nbsp;col-7</div>
<div>1.01&nbsp; &nbsp; 0.95&nbsp; &nbsp; 0.96&nbsp; &nbsp; 0.97&nbsp; &nbsp; 0.93&nbsp; &nbsp; 0.95&nbsp; &nbsp; <span style="background-color: #d9ead3">3.06</span>&nbsp; &nbsp; read-only-count</div>
<div>1.00&nbsp; &nbsp; 0.98&nbsp; &nbsp; 0.98&nbsp; &nbsp; 0.98&nbsp; &nbsp; 0.96&nbsp; &nbsp; 0.98&nbsp; &nbsp; <span style="background-color: #fff2cc">0.95</span>&nbsp; &nbsp; read-only-distinct</div>
<div>1.00&nbsp; &nbsp; 0.98&nbsp; &nbsp; 0.98&nbsp; &nbsp; 1.00&nbsp; &nbsp; 0.99&nbsp; &nbsp; 0.99&nbsp; &nbsp; 0.97&nbsp; &nbsp; read-only-order</div>
<div>0.99&nbsp; &nbsp; 1.00&nbsp; &nbsp; 1.01&nbsp; &nbsp; 1.00&nbsp; &nbsp; 1.01&nbsp; &nbsp; 0.99&nbsp; &nbsp; 1.00&nbsp; &nbsp; read-only_range=10</div>
<div>0.99&nbsp; &nbsp; 1.00&nbsp; &nbsp; 1.00&nbsp; &nbsp; 1.00&nbsp; &nbsp; 1.01&nbsp; &nbsp; 1.00&nbsp; &nbsp; 0.99&nbsp; &nbsp; read-only_range=100</div>
<div>1.00&nbsp; &nbsp; 0.97&nbsp; &nbsp; 1.02&nbsp; &nbsp; 1.03&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.05&nbsp; &nbsp; 1.03&nbsp; &nbsp; read-only_range=10000</div>
<div>1.00&nbsp; &nbsp; 0.97&nbsp; &nbsp; 0.99&nbsp; &nbsp; 0.97&nbsp; &nbsp; 0.95&nbsp; &nbsp; 0.98&nbsp; &nbsp; <span style="background-color: #fff2cc">0.96</span>&nbsp; &nbsp; read-only-simple</div>
<div>1.00&nbsp; &nbsp; 0.96&nbsp; &nbsp; 0.97&nbsp; &nbsp; 0.97&nbsp; &nbsp; 0.94&nbsp; &nbsp; 0.97&nbsp; &nbsp; <span style="background-color: #fff2cc">0.95</span>&nbsp; &nbsp; read-only-sum</div>
<p></p></span></span></div>
<p></p></span></span></div>
<div><span><span><br></span></span></div>
<div><span><span>
<div><span><span><b>Results: writes, version 17 to 19</b></span></span></div>
<div><span><span><br></span></span></div>
<div>Summary</div>
<div>
<ul style="text-align: left">
<li>there are no regressions</li>
</ul>
</div>
<div><span style="font-family: courier;font-size: small">Relative to: PG 17.10</span></div>
<div><span><span style="font-family: courier;font-size: x-small">
<div>col-1 : PG 18.4</div>
<div>col-2 : PG 19 beta1</div>
<div></div>
<div>
<div>col-1&nbsp; &nbsp;col-2</div>
<div>0.99&nbsp; &nbsp; 0.99&nbsp; &nbsp; delete</div>
<div>1.02&nbsp; &nbsp; 1.02&nbsp; &nbsp; insert</div>
<div>1.00&nbsp; &nbsp; 0.98&nbsp; &nbsp; read-write_range=10</div>
<div>0.99&nbsp; &nbsp; 0.99&nbsp; &nbsp; read-write_range=100</div>
<div>1.01&nbsp; &nbsp; 1.03&nbsp; &nbsp; update-index</div>
<div>1.01&nbsp; &nbsp; 0.98&nbsp; &nbsp; update-inlist</div>
<div>0.98&nbsp; &nbsp; 1.01&nbsp; &nbsp; update-nonindex</div>
<div>1.01&nbsp; &nbsp; 1.03&nbsp; &nbsp; update-one</div>
<div>1.00&nbsp; &nbsp; 1.00&nbsp; &nbsp; update-zipf</div>
<div>0.97&nbsp; &nbsp; 0.99&nbsp; &nbsp; write-only</div>
</div>
<p></p></span></span></div>
<div><span><span>
<div><span><span><br></span></span></div>
<div><span><span><b>Results: writes, version 12 to 19</b></span></span></div>
<div><span><span><b><br></b></span></span></div>
<div>Summary</div>
<div>
<ul style="text-align: left">
<li>there are no regressions</li>
<li>many large improvements arrived in version 17 and remain in 19 beta1</li>
</ul>
</div>
<div><span style="font-family: courier;font-size: small">Relative to: PG 12.22</span></div>
<div><span><span style="font-family: courier;font-size: x-small">
<div>col-1 : PG 13.23</div>
<div>col-2 : PG 14.23</div>
<div>col-3 : PG 15.18</div>
<div>col-4 : PG 16.14</div>
<div>col-5 : PG 17.10</div>
<div>col-6 : PG 18.4</div>
<div>col-7 : PG 19 beta1</div>
<div></div>
<p></p></span></span></div>
<div><span><span style="font-family: courier;font-size: x-small">
<div>col-1&nbsp; &nbsp;col-2&nbsp; &nbsp;col-3&nbsp; &nbsp;col-4&nbsp; &nbsp;col-5&nbsp; &nbsp;col-6&nbsp; &nbsp;col-7</div>
<div>0.99&nbsp; &nbsp; 1.11&nbsp; &nbsp; 1.13&nbsp; &nbsp; 1.10&nbsp; &nbsp; <span style="background-color: #d9ead3">1.28</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.27</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.27</span>&nbsp; &nbsp; delete</div>
<div>1.02&nbsp; &nbsp; 1.17&nbsp; &nbsp; 1.16&nbsp; &nbsp; 1.19&nbsp; &nbsp; <span style="background-color: #d9ead3">1.23</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.25</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.25</span>&nbsp; &nbsp; insert</div>
<div>1.00&nbsp; &nbsp; 1.20&nbsp; &nbsp; 1.22&nbsp; &nbsp; 1.20&nbsp; &nbsp; <span style="background-color: #d9ead3">1.24</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.24</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.22</span>&nbsp; &nbsp; read-write_range=10</div>
<div>0.99&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.05&nbsp; &nbsp; 1.04&nbsp; &nbsp; 1.06&nbsp; &nbsp; 1.05&nbsp; &nbsp; 1.04&nbsp; &nbsp; read-write_range=100</div>
<div>0.98&nbsp; &nbsp; 1.08&nbsp; &nbsp; 1.05&nbsp; &nbsp; 0.94&nbsp; &nbsp; <span style="background-color: #d9ead3">1.84</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.85</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.90</span>&nbsp; &nbsp; update-index</div>
<div>1.00&nbsp; &nbsp; 1.07&nbsp; &nbsp; 1.06&nbsp; &nbsp; 1.05&nbsp; &nbsp; 1.12&nbsp; &nbsp; 1.13&nbsp; &nbsp; 1.10&nbsp; &nbsp; update-inlist</div>
<div>1.01&nbsp; &nbsp; 1.07&nbsp; &nbsp; 1.07&nbsp; &nbsp; 0.86&nbsp; &nbsp; <span style="background-color: #d9ead3">1.87</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.84</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.88</span>&nbsp; &nbsp; update-nonindex</div>
<div>1.04&nbsp; &nbsp; 0.96&nbsp; &nbsp; 0.96&nbsp; &nbsp; 1.10&nbsp; &nbsp; <span style="background-color: #d9ead3">1.39</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.41</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.43</span>&nbsp; &nbsp; update-one</div>
<div>1.01&nbsp; &nbsp; 1.05&nbsp; &nbsp; 1.07&nbsp; &nbsp; 0.96&nbsp; &nbsp; <span style="background-color: #d9ead3">1.63</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.62</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.63</span>&nbsp; &nbsp; update-zipf</div>
<div>0.99&nbsp; &nbsp; 1.11&nbsp; &nbsp; 1.13&nbsp; &nbsp; 1.09&nbsp; &nbsp; <span style="background-color: #d9ead3">1.41</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.37</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.40</span>&nbsp; &nbsp; write-only</div>
<p></p></span></span></div>
<p></p></span></span></div>
<p></p></span></span></div>
<p></p></span></div>

<p><a href="https://smalldatum.blogspot.com/2026/06/cpu-bound-sysbench-on-large-server.html">CPU-bound sysbench on a large server: Postgres 12 to 19 beta1</a> appeared first on <a 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><a 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 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><a 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 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><a 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 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><a 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 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><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://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 loading="lazy" decoding="async" 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 loading="lazy" decoding="async" 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><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://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>The insert benchmark on a small server, IO-bound workload : Postgres 19 beta1</title>
      <link rel="alternate" type="text/html" href="https://smalldatum.blogspot.com/2026/06/the-insert-benchmark-on-small-server-io.html" />
      <id>https://smalldatum.blogspot.com/2026/06/the-insert-benchmark-on-small-server-io.html</id>
      <updated>2026-06-17T01:29:04+00:00</updated>
      <author><name>Mark Callaghan</name></author>
      <summary type="html"><![CDATA[<p>This has results for Postgres versions 19 beta1, 18.4 and 17.10 with the Insert Benchmark on a small server using a cached and CPU-bound workload. I also used MySQL 8.4.8 to see where performance was different.Postgres continues to be boring in a good way. It is hard to find performance regressions. tl;drcreate index (the l.x step) is faster in Postgres 19beta1. A Postgres expert told me that the sort algorithm was changed to be more CPU efficientthe write heavy steps (l.i1, l.i2) are 15% and 9% faster in 19 beta1 vs Postgres 17.10the second write heavy step (l.i2) is more than 20X faster in MySQL 8.4.8 vs Postgres thanks to the CPU overhead from get_actual_variable_range. I have written about this before.Builds, configuration and hardwareI compiled Postgres from source using -O2 -fno-omit-frame-pointer for versions 19 beta1, 18.4 and 17.10.I compiled MySQL 8.4.8 from source as well.The server is an Beelink SER7 with a Ryzen 7 7840HS CPU with 8 cores and AMD SMT disabled, 32G of RAM. Storage is one SSD for the OS and an NVMe SSD for the database using ext-4 with discard enabled. The OS is Ubuntu 24.04.For 17.10 the config file is named conf.diff.cx10a_c8r32 (cx10a) and is here.For Postgres 18 and 19 the config file is conf.diff.cx10b_c8r32 (cx10b) which is as similar as possible to the config for version 17.For MySQL 8.4.8 the config file is my.cnf.cz12a_c8r32.The BenchmarkThe benchmark is explained here and is run with 1 client.The point query (qp100, qp500, qp1000) and range query (qr100, qr500, qr1000) steps are run for 3600 seconds each.The benchmark steps are:l.i0insert 800M rows per table in PK order. The table has a PK index but no secondary indexes. There is one connection per client.l.xcreate 3 secondary indexes per table. There is one connection per client.l.i1use 2 connections/client. One inserts 4M rows per table and the other does deletes at the same rate as the inserts. Each transaction modifies 50 rows (big transactions). This step is run for a fixed number of inserts, so the run time varies depending on the insert rate.l.i2like l.i1 but each transaction modifies 5 rows (small transactions) and 1M rows are inserted and deleted per table.Wait for S seconds after the step finishes to reduce variance during the read-write benchmark steps that follow. The value of S is a function of the table size.qr100use 3 connections/client. One does range queries and performance is reported for this. The second does does 100 inserts/s and the third does 100 deletes/s. The second and third are less busy than the first. The range queries use covering secondary indexes. If the target insert rate is not sustained then that is considered to be an SLA failure. If the target insert rate is sustained then the step does the same number of inserts for all systems tested. This step is frequently not IO-bound for the IO-bound workload.qp100like qr100 except uses point queries on the PK indexqr500like qr100 but the insert and delete rates are increased from 100/s to 500/sqp500like qp100 but the insert and delete rates are increased from 100/s to 500/sqr1000like qr100 but the insert and delete rates are increased from 100/s to 1000/sqp1000like qp100 but the insert and delete rates are increased from 100/s to 1000/sResultsThe performance summary with charts is here.This table lists relative QPS per benchmark step and relative QPS is:    (QPS for my version / QPS for Postgres 17.10)The background in the table cells is blue for big improvements and yellow for regressions. There are no regressions here. The improvements here for Postgres 19 beta1 are similar to what I reported for the cached workload.The index create (l.x) step is much faster in 19.10. I usually ignore results on this step but I am curious if something was done in 19.10 to improve index create. A Postgres expert told me that the sort algorithm for index create was changed in version 19 to be more CPU efficient.For the write-heavy steps (l.i1, l.i2):there are large improvements in 19 beta1 (15% and 9%). The CPU overhead is lower in 19 beta1 compared to 17.10 (see cpupq here).throughput for the l.i2 step is more than 20X larger for MySQL than for Postgres. From vmstat I see that the CPU overhead (cpupq here) is more than 10X larger with Postgres vs MySQL. From flamegraphs the problem is the CPU overhead in get_actual_variable_range. I have written about this before (see here). The Postgres query planner uses too much CPU skipping old versions to figure out selectivity for a query and there are too many old versions because Postgres doesn\'t collect them ASAP, vacuum takes time. The flamegraphs are in subdirectories here.For the range query steps (qr100, qr500, qr1000) throughput is ~3% less in 19 beta1 vs 17.10 and ~1% less in 18.4 vs 17.10. For 19 beta1 there is a small increase in CPU overhead (see cpupq here, here and here). I already have flamegraphs for MySQL 8.4.8 and Postgres 19 beta1, soon I will have them for Postgres 17.10 and 18.4 to try and explain this.dbmsl.i0l.xl.i1l.i2qr100qp100qr500qp500qr1000qp1000PG 17.101.001.001.001.001.001.001.001.001.001.00PG 18.41.011.031.001.000.981.000.990.990.990.99PG 19 beta11.011.151.051.090.971.010.961.010.971.00MySQL 8.4.80.770.890.7621.620.611.070.660.930.850.84</p>
<p><a href="https://smalldatum.blogspot.com/2026/06/the-insert-benchmark-on-small-server-io.html">The insert benchmark on a small server, IO-bound workload : Postgres 19 beta1</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>This has results for Postgres versions 19 beta1, 18.4 and 17.10 with the <a href="https://smalldatum.blogspot.com/2023/12/updates-for-insert-benchmark-december.html">Insert Benchmark</a> on a small server using a cached and CPU-bound workload. I also used MySQL 8.4.8 to see where performance was different.</p>

<p>Postgres continues to be boring in a good way. It is hard to find performance regressions.</p>
<p>&nbsp;tl;dr</p>

<ul style="text-align: left">
<li>create index (the l.x step) is faster in Postgres 19beta1. A Postgres expert told me that the sort algorithm was changed to be more CPU efficient</li>
<li>the write heavy steps (l.i1, l.i2) are 15% and 9% faster in 19 beta1 vs Postgres 17.10</li>
<li>the second write heavy step (l.i2) is more than 20X faster in MySQL 8.4.8 vs Postgres thanks to the CPU overhead from get_actual_variable_range. I have <a href="https://www.google.com/search?q=site%3Asmalldatum.blogspot.com+get_actual_variable_range">written about this</a> before.</li>
</ul>
<div><b>Builds, configuration and hardware</b></div>
<div>
<div></div>
<div>I compiled Postgres from source using&nbsp;<i>-O2 -fno-omit-frame-pointer</i>&nbsp;for versions 19 beta1, 18.4 and 17.10.
<p>I compiled MySQL 8.4.8 from source as well.</p></div>
<div>The server is an Beelink SER7 with a Ryzen 7 7840HS CPU with 8 cores and AMD SMT disabled, 32G of RAM. Storage is one SSD for the OS and an NVMe SSD for the database using ext-4 with discard enabled. The OS is Ubuntu 24.04.</div>
<div></div>
<div>For 17.10 the config file is named conf.diff.cx10a_c8r32 (cx10a) and&nbsp;<a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c8r32/pg172_o2nofp/conf.diff.cx10a_c8r32">is here</a>.</div>

<div>For Postgres 18 and 19&nbsp;the config file is&nbsp;<a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c8r32/pg18_o2nofp/conf.diff.cx10b_c8r32">conf.diff.cx10b_c8r32</a>&nbsp;(cx10b) which is as similar as possible to the config for version 17.</div>
</div>
<div></div>
<div>For MySQL 8.4.8 the config file is <a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c8r32/my8406_rel_o2nofp/etc/my.cnf.cz12a_c8r32">my.cnf.cz12a_c8r32</a>.</div>
<div></div>
<div>
<div><b>The Benchmark</b></div>
<div>
<div></div>
<div>The benchmark is <a href="https://smalldatum.blogspot.com/2023/12/updates-for-insert-benchmark-december.html">explained here</a> and is run with 1 client.</div>
<div></div>
<div>The point query (qp100, qp500, qp1000) and range query (qr100, qr500, qr1000) steps are run for 3600 seconds each.</div>
<div></div>
<div>The benchmark steps are:</div>
<div>
<div>
<ul>
<li>l.i0</li>
<ul>
<li>insert 800M rows per table in PK order. The table has a PK index but no secondary indexes. There is one connection per client.</li>
</ul>
<li>l.x</li>
<ul>
<li>create 3 secondary indexes per table. There is one connection per client.</li>
</ul>
<li>l.i1</li>
<ul>
<li>use 2 connections/client. One inserts 4M rows per table and the other does deletes at the same rate as the inserts. Each transaction modifies 50 rows (big transactions). This step is run for a fixed number of inserts, so the run time varies depending on the insert rate.</li>
</ul>
<li>l.i2</li>
<ul>
<li>like l.i1 but each transaction modifies 5 rows (small transactions) and 1M rows are inserted and deleted per table.</li>
<li>Wait for S seconds after the step finishes to reduce variance during the read-write benchmark steps that follow. The value of S is a function of the table size.</li>
</ul>
<li>qr100</li>
<ul>
<li>use 3 connections/client. One does range queries and performance is reported for this. The second does does 100 inserts/s and the third does 100 deletes/s. The second and third are less busy than the first. The range queries use covering secondary indexes. If the target insert rate is not sustained then that is considered to be an SLA failure. If the target insert rate is sustained then the step does the same number of inserts for all systems tested. This step is frequently not IO-bound for the IO-bound workload.</li>
</ul>
<li>qp100</li>
<ul>
<li>like qr100 except uses point queries on the PK index</li>
</ul>
<li>qr500</li>
<ul>
<li>like qr100 but the insert and delete rates are increased from 100/s to 500/s</li>
</ul>
<li>qp500</li>
<ul>
<li>like qp100 but the insert and delete rates are increased from 100/s to 500/s</li>
</ul>
<li>qr1000</li>
<ul>
<li>like qr100 but the insert and delete rates are increased from 100/s to 1000/s</li>
</ul>
<li>qp1000</li>
<ul>
<li>like qp100 but the insert and delete rates are increased from 100/s to 1000/s</li>
</ul>
</ul>
<div>
<div><b>Results</b></div>
<div></div>
<div>The performance summary with charts <a href="https://mdcallag.github.io/reports/jun26.ib.pn53.io.800m.5m.3600s.1u.pg.my/all.html#summary">is here</a>.</div>
<div></div>
<div>This table lists relative QPS per benchmark step and relative QPS is:<br>&nbsp; &nbsp; (QPS for my version / QPS for Postgres 17.10)
<p>The background in the table cells is blue for big improvements and yellow for regressions. There are no regressions here.&nbsp;</p></div>
<div></div>
<div>The improvements here for Postgres 19 beta1 are similar to <a href="https://smalldatum.blogspot.com/2026/06/the-insert-benchmark-on-small-server.html">what I reported</a> for the cached workload.</div>
<div></div>
<div>The index create (l.x) step is much faster in 19.10. I usually ignore results on this step but I am curious if something was done in 19.10 to improve index create. A Postgres expert told me that the sort algorithm for index create was changed in version 19 to be more CPU efficient.</div>
<div></div>
<div>For the write-heavy steps (l.i1, l.i2):</div>
<div>
<ul style="text-align: left">
<li>there are large improvements in 19 beta1 (15% and 9%). The CPU overhead is lower in 19 beta1 compared to 17.10 (<a href="https://mdcallag.github.io/reports/jun26.ib.pn53.io.800m.5m.3600s.1u.pg.my/all.html#l.i1.metrics">see cpupq here</a>).</li>
<li>throughput for the l.i2 step is more than 20X larger for MySQL than for Postgres. From vmstat I see that the CPU overhead (<a href="https://mdcallag.github.io/reports/jun26.ib.pn53.io.800m.5m.3600s.1u.pg.my/all.html#l.i2.metrics">cpupq here</a>) is more than 10X larger with Postgres vs MySQL. From flamegraphs the problem is the CPU overhead in get_actual_variable_range. I have written about this before (<a href="https://www.google.com/search?q=site%3Asmalldatum.blogspot.com+get_actual_variable_range">see here</a>). The Postgres query planner uses too much CPU skipping old versions to figure out selectivity for a query and there are too many old versions because Postgres doesn&rsquo;t collect them ASAP, vacuum takes time. The flamegraphs are in <a href="https://github.com/mdcallag/mytools/tree/master/bench/arc/jun26.pn53.ib.pg19b1.my848/io/svg.all">subdirectories here</a>.</li>
</ul>
</div>
<div>For the range query steps (qr100, qr500, qr1000) throughput is ~3% less in 19 beta1 vs 17.10 and ~1% less in 18.4 vs 17.10. For 19 beta1 there is a small increase in CPU overhead (see cpupq <a href="https://mdcallag.github.io/reports/jun26.ib.pn53.io.800m.5m.3600s.1u.pg.my/all.html#qr100.L1.metrics">here</a>, <a href="https://mdcallag.github.io/reports/jun26.ib.pn53.io.800m.5m.3600s.1u.pg.my/all.html#qr500.L3.metrics">here</a> and <a href="https://mdcallag.github.io/reports/jun26.ib.pn53.io.800m.5m.3600s.1u.pg.my/all.html#qr1000.L5.metrics">here</a>). I already have flamegraphs for MySQL 8.4.8 and Postgres 19 beta1, soon I will have them for Postgres 17.10 and 18.4 to try and explain this.</div>
<div></div>
<div>
<div>
<table border="1" cellpadding="8" style="color: black">
<tbody>
<tr>
<th><span style="font-size: x-small">dbms</span></th>
<th><span style="font-size: x-small">l.i0</span></th>
<th><span style="font-size: x-small">l.x</span></th>
<th><span style="font-size: x-small">l.i1</span></th>
<th><span style="font-size: x-small">l.i2</span></th>
<th><span style="font-size: x-small">qr100</span></th>
<th><span style="font-size: x-small">qp100</span></th>
<th><span style="font-size: x-small">qr500</span></th>
<th><span style="font-size: x-small">qp500</span></th>
<th><span style="font-size: x-small">qr1000</span></th>
<th><span style="font-size: x-small">qp1000</span></th>
</tr>
<tr>
<td style="text-align: right"><span style="font-size: x-small">PG 17.10</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
</tr>
<tr>
<td style="text-align: right"><span style="font-size: x-small">PG 18.4</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.01</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.03</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">0.98</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">0.99</span></td>
<td style="text-align: right"><span style="font-size: x-small">0.99</span></td>
<td style="text-align: right"><span style="font-size: x-small">0.99</span></td>
<td style="text-align: right"><span style="font-size: x-small">0.99</span></td>
</tr>
<tr>
<td style="text-align: right"><span style="font-size: x-small">PG 19 beta1</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.01</span></td>
<td id="chi" style="background-color: #81fff9;text-align: right"><span style="font-size: x-small">1.15</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.05</span></td>
<td id="chi" style="background-color: #81fff9;text-align: right"><span style="font-size: x-small">1.09</span></td>
<td style="text-align: right"><span style="font-size: x-small">0.97</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.01</span></td>
<td style="text-align: right"><span style="font-size: x-small">0.96</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.01</span></td>
<td style="text-align: right"><span style="font-size: x-small">0.97</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
</tr>
<tr>
<td style="text-align: right"><span style="font-size: x-small">MySQL 8.4.8</span></td>
<td id="clo" style="background-color: #ffdd81;text-align: right"><span style="font-size: x-small">0.77</span></td>
<td id="clo" style="background-color: #ffdd81;text-align: right"><span style="font-size: x-small">0.89</span></td>
<td id="clo" style="background-color: #ffdd81;text-align: right"><span style="font-size: x-small">0.76</span></td>
<td id="chi" style="background-color: #81fff9;text-align: right"><span style="font-size: x-small">21.62</span></td>
<td id="clo" style="background-color: #ffdd81;text-align: right"><span style="font-size: x-small">0.61</span></td>
<td id="chi" style="background-color: #81fff9;text-align: right"><span style="font-size: x-small">1.07</span></td>
<td id="clo" style="background-color: #ffdd81;text-align: right"><span style="font-size: x-small">0.66</span></td>
<td id="clo" style="background-color: #ffdd81;text-align: right"><span style="font-size: x-small">0.93</span></td>
<td id="clo" style="background-color: #ffdd81;text-align: right"><span style="font-size: x-small">0.85</span></td>
<td id="clo" style="background-color: #ffdd81;text-align: right"><span style="font-size: x-small">0.84</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

<p><a href="https://smalldatum.blogspot.com/2026/06/the-insert-benchmark-on-small-server-io.html">The insert benchmark on a small server, IO-bound workload : Postgres 19 beta1</a> appeared first on <a 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><a 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 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><a 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 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><a 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 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><a 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 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><a 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 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><a 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 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><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://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><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://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><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/">MariaDB Java Connector 3.5.9, 3.4.3, 3.3.5, and 2.7.14 now available</a> appeared first on <a 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><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/">MariaDB Java Connector 3.5.9, 3.4.3, 3.3.5, and 2.7.14 now available</a> appeared first on <a 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><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://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 loading="lazy" decoding="async" 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 loading="lazy" decoding="async" 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 loading="lazy" decoding="async" 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><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://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><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://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><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://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><a 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 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><a 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 href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>HammerDB tproc-c on a large server, Postgres 14 to 19 beta1</title>
      <link rel="alternate" type="text/html" href="https://smalldatum.blogspot.com/2026/06/hammerdb-tproc-c-on-large-server.html" />
      <id>https://smalldatum.blogspot.com/2026/06/hammerdb-tproc-c-on-large-server.html</id>
      <updated>2026-06-13T01:00:03+00:00</updated>
      <author><name>Mark Callaghan</name></author>
      <summary type="html"><![CDATA[<p>This has results for HammerDB tproc-c on a large server using MySQL and Postgres. I am new to HammerDB and still figuring out how to explain and present results so I will keep this simple and just share graphs without explaining the results.tl;drThere are small regressions in versions 16, 17 and 18NOPM usually improves a small amount in 19 beta1 relative to 18Builds, configuration and hardwareI compiled Postgres versions from source: 14.22, 14.23, 15.17, 15.18, 16.13, 16.14, 17.9, 17.10, 18.0, 18.1, 18.2, 18.3, 18.4 and 19 beta1.I used a 48-core server from Hetzneran ax162s with an AMD EPYC 9454P 48-Core Processor with SMT disabled2 Intel D7-P5520 NVMe storage devices with RAID 1 (3.8T each) using ext4128G RAMUbuntu 24.04Postgres configuration files:prior to version 18 the config file is named conf.diff.cx10a50g_c32r128 (x10a_c32r128) and is here for versions 14, 15, 16 and 17.for Postgres 18 and 19 I used conf.diff.cx10b_c32r128 (x10b_c32r128) with io_method=sync to be similar to the config used for versions 14 through 17.BenchmarkThe benchmark is tproc-c from HammerDB. The tproc-c benchmark is derived from TPC-C.The benchmark was run for several workloads:vu=10, wh=1000 - 10 virtual users, 1000 warehousesvu=20, wh=1000 - 20 virtual users, 1000 warehousesvu=40, wh=1000 - 40 virtual users, 1000 warehousesvu=10, wh=2000 - 10 virtual users, 2000 warehousesvu=20, wh=2000 - 20 virtual users, 2000 warehousesvu=40, wh=2000 - 40 virtual users, 2000 warehousesvu=10, wh=4000 - 10 virtual users, 4000 warehousesvu=20, wh=4000 - 20 virtual users, 4000 warehousesvu=40, wh=4000 - 40 virtual users, 4000 warehousesThe wh=1000 workloads are less heavy on IO. The wh=4000 workloads are more heavy on IO.The benchmark for Postgres is run by a variant of this script which depends on scripts here.stored procedures are enabledpartitioning is used because the warehouse count is >= 1000a 5 minute rampup is usedthen performance is measured for 60 minutesResultsMy analysis at this point is simple -- I only consider average throughput. Eventually I will examine throughput over time and efficiency (CPU and IO).On the charts that follow y-axis does not start at 0 to improve readability at the risk of overstating the differences. The y-axis shows relative throughput. There might be a regression when the relative throughput is less than 1.0. There might be an improvement when it is > 1.0. The relative throughput is:(NOPM for some-version / NOPM for base-version)The base version is Postgres 14.22.A spreadsheet with absolute and relative values for NOPM is here.Results: vu=10, wh=1000Summary:There are small regressions in versions 16, 17 and 18 while NOPM improves is 19 beta1Results: vu=20, wh=1000Summary:There are small regressions in versions 16, 17 and 18 while NOPM improves is 19 beta1Results: vu=40, wh=1000Summary:There are small regressions in versions 17 and 18 while NOPM improves is 19 beta1Results: vu=10, wh=2000Summary:There are small regressions in version 18 while NOPM improves is 19 beta1Results: vu=20, wh=2000Summary:There are small regressions in versions 16, 17 and 18 while NOPM improves is 19 beta1Results: vu=40, wh=2000Summary:There are small regressions in versions 16, 17 and 18 while NOPM improves is 19 beta1There is no result for 18.1 because of a bug in my test scriptsResults: vu=10, wh=4000Summary:There are small regressions in versions 16, 17 and 18 while NOPM improves is 19 beta1Results: vu=20, wh=4000Summary:There are small regressions in versions 16, 17 and 18Results: vu=40, wh=4000Summary:There are small regressions in versions 16, 17 and 18 while NOPM improves is 19 beta1</p>
<p><a href="https://smalldatum.blogspot.com/2026/06/hammerdb-tproc-c-on-large-server.html">HammerDB tproc-c on a large server, Postgres 14 to 19 beta1</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>This has results for <a href="https://www.hammerdb.com/">HammerDB</a> tproc-c on a large server using MySQL and Postgres. I am new to HammerDB and still figuring out how to explain and present results so I will keep this simple and just share graphs without explaining the results.</p>
<p>tl;dr</p>

<ul style="text-align: left"></ul>

<ul style="text-align: left">
<li>There are small regressions in versions 16, 17 and 18</li>
<li>NOPM usually improves a small amount in 19 beta1 relative to 18</li>
</ul>
<div><b>Builds, configuration and hardware</b></div>
<div>
<div>
<div>
<div></div>
<div>I compiled Postgres versions from source: 14.22, 14.23, 15.17, 15.18, 16.13, 16.14, 17.9, 17.10, 18.0, 18.1, 18.2, 18.3, 18.4 and 19 beta1.</div>
</div>
<div></div>
<div>
<div>I used a 48-core server from Hetzner</div>
<div>
<ul>
<li>an ax162s with an AMD EPYC 9454P 48-Core Processor with SMT disabled</li>
<li>2 Intel D7-P5520 NVMe storage devices with RAID 1 (3.8T each) using ext4</li>
<li>128G RAM</li>
<li>Ubuntu 24.04</li>
</ul>
<div>
<div><span style="font-family: inherit">Postgres configuration files:</span></div>
<div>
<ul>
<li><span style="font-family: inherit">prior to version 18 the config file is named conf.diff.cx10a50g_c32r128 (x10a_c32r128) and is here for versions </span><a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg1412_o2nofp/conf.diff.cx10a50g_c32r128">14</a>, <a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg157_o2nofp/conf.diff.cx10a50g_c32r128">15</a>, <a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg163_o2nofp/conf.diff.cx10a50g_c32r128">16</a> and <a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg17beta1_o2nofp/conf.diff.cx10a50g_c32r128">17</a>.</li>
<li>for Postgres 18 and 19 I used <a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg18beta3_o2nofp/conf.diff.cx10b50g_c32r128">conf.diff.cx10b_c32r128</a> (x10b_c32r128) with io_method=sync to be similar to the config used for versions 14 through 17.</li>
</ul>
<div>
<div><b>Benchmark</b>
<div></div>
</div>
<div></div>
<div>The benchmark is <a href="https://www.hammerdb.com/docs/ch03.html">tproc-c</a> from <a href="https://www.hammerdb.com/">HammerDB</a>. The tproc-c benchmark is derived from TPC-C.
<p>The benchmark was run for several workloads:</p></div>
<div>
<ul>
<li>vu=10, wh=1000 &ndash; 10 virtual users, 1000 warehouses</li>
<li>vu=20, wh=1000 &ndash; 20 virtual users, 1000 warehouses</li>
<li>vu=40, wh=1000 &ndash; 40 virtual users, 1000 warehouses</li>
<li>vu=10, wh=2000 &ndash; 10 virtual users, 2000 warehouses</li>
<li>vu=20, wh=2000 &ndash; 20 virtual users, 2000 warehouses</li>
<li>vu=40, wh=2000 &ndash; 40 virtual users, 2000 warehouses</li>
<li>vu=10, wh=4000 &ndash; 10 virtual users, 4000 warehouses</li>
<li>vu=20, wh=4000 &ndash; 20 virtual users, 4000 warehouses</li>
<li>vu=40, wh=4000 &ndash; 40 virtual users, 4000 warehouses</li>
</ul>
<div>The wh=1000 workloads are less heavy on IO. The wh=4000 workloads are more heavy on IO.</div>
<div></div>
<div>The benchmark for Postgres is run by a variant of <a href="https://github.com/mdcallag/mytools/blob/master/bench/arc/jan26.tprocc.pn53.pg/allpg.N.sh">this script</a> which depends on <a href="https://github.com/mdcallag/mytools/tree/master/bench/arc/jan26.tprocc.pn53.pg/testscripts">scripts here</a>.</div>
<div>
<ul>
<li>stored procedures are enabled</li>
<li>partitioning is used because the warehouse count is &gt;= 1000</li>
<li>a 5 minute rampup is used</li>
<li>then performance is measured for 60 minutes</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<div><b>Results</b></div>
<div><b><br></b></div>
<div>My analysis at this point is simple &mdash; I only consider average throughput. Eventually I will examine throughput over time and efficiency (CPU and IO).</div>
<div></div>
<div>On the charts that follow y-axis does not start at 0 to improve readability <b>at the risk of overstating the differences</b>. The y-axis shows relative throughput. There might be a regression when the relative throughput is less than 1.0. There might be an improvement when it is &gt; 1.0. The relative throughput is:</div>
</div>
<blockquote style="border-color: currentcolor;border-style: none;border-width: medium;border: none;margin: 0px 0px 0px 40px;padding: 0px"><p>(NOPM for <i>some-version</i> / NOPM for <i>base-version</i>)</p></blockquote>
<p>The base version is Postgres 14.22.</p>
<p>A spreadsheet with absolute and relative values for NOPM <a href="https://docs.google.com/spreadsheets/d/1OTrl-jTwfBUzl32B6R2Az2WL8ysDy6f2Iljg3zMfdAo/edit?usp=sharing">is here</a>.</p>
<p><b>Results: vu=10, wh=1000</b></p>
<p>Summary:</p>


<ul style="text-align: left">
<li>There are small regressions in versions 16, 17 and 18 while NOPM improves is 19 beta1</li>
</ul>
<div class="separator" style="clear: both;text-align: center"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgm3Ts72epDSYxW4mDZw_9bNJ5dPgLwMn07XAKtJWiGhIro3-Jo5e7lEdSFrKexARX1da3mGuaR17ANgwpudMAXGvDCGats5zdNANPFge4cT2-au-utPsMrHiXHucKtNU8aoQbQGSt_108RIwqy1EmmSyBWcEN8fcPhEwGlDE4C5AmbDmUkv9rltQ-i5j2W/s600/relative%20NOPM_%201000%20warehouses,%2010%20virtual%20users.png" style="margin-left: 1em;margin-right: 1em"><img loading="lazy" decoding="async" border="0" data-original-height="371" data-original-width="600" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgm3Ts72epDSYxW4mDZw_9bNJ5dPgLwMn07XAKtJWiGhIro3-Jo5e7lEdSFrKexARX1da3mGuaR17ANgwpudMAXGvDCGats5zdNANPFge4cT2-au-utPsMrHiXHucKtNU8aoQbQGSt_108RIwqy1EmmSyBWcEN8fcPhEwGlDE4C5AmbDmUkv9rltQ-i5j2W/w640-h396/relative%20NOPM_%201000%20warehouses,%2010%20virtual%20users.png" width="640"></a></div>
<p><b>Results: vu=20, wh=1000</b></p>
<p>Summary:</p>


<ul style="text-align: left">
<li>There are small regressions in versions 16, 17 and 18 while NOPM improves is 19 beta1</li>
</ul>
<div class="separator" style="clear: both;text-align: center"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4g__41do5Fskg5kucMPbOZOMgUKsBc11ntmPhSsmQxUA2XBFfJLnn0YrHrprKtJPgOcHEA-viSbbVnStEHbL5Gy2GWxaYRzfQQ76EJDLUiFyMn2XqnEao9K5ZB2Ne5fOY304OE8hMxAhtRcHOye8g0P7U8Z9iFTde_nc6DIznuZM_CDdDxDScZ5oQgsUk/s600/relative%20NOPM_%201000%20warehouses,%2020%20virtual%20users.png" style="margin-left: 1em;margin-right: 1em"><img loading="lazy" decoding="async" border="0" data-original-height="371" data-original-width="600" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4g__41do5Fskg5kucMPbOZOMgUKsBc11ntmPhSsmQxUA2XBFfJLnn0YrHrprKtJPgOcHEA-viSbbVnStEHbL5Gy2GWxaYRzfQQ76EJDLUiFyMn2XqnEao9K5ZB2Ne5fOY304OE8hMxAhtRcHOye8g0P7U8Z9iFTde_nc6DIznuZM_CDdDxDScZ5oQgsUk/w640-h396/relative%20NOPM_%201000%20warehouses,%2020%20virtual%20users.png" width="640"></a></div>
<p><b>Results: vu=40, wh=1000</b></p>
<p>Summary:</p>


<ul style="text-align: left">
<li>There are small regressions in versions 17 and 18 while NOPM improves is 19 beta1</li>
</ul>
<div class="separator" style="clear: both;text-align: center"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZf60hioew3Mutsk-R2jGKFh1uvw-ljdyuN_-HEnulX34bCAJZlzLpyXkAuwynurAluRi_m6wCw5aKH6raptWcN16G13Fgol_8a-WV4gMslq4WIKIJ5EAcKK3l5Df7SVXF60ev13SuyFemwOaGn9jh1lNlHAW5TKCY745rCdgK-BDzQGfdt0WDkP2QxPtj/s600/relative%20NOPM_%201000%20warehouses,%2040%20virtual%20users.png" style="margin-left: 1em;margin-right: 1em"><img loading="lazy" decoding="async" border="0" data-original-height="371" data-original-width="600" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZf60hioew3Mutsk-R2jGKFh1uvw-ljdyuN_-HEnulX34bCAJZlzLpyXkAuwynurAluRi_m6wCw5aKH6raptWcN16G13Fgol_8a-WV4gMslq4WIKIJ5EAcKK3l5Df7SVXF60ev13SuyFemwOaGn9jh1lNlHAW5TKCY745rCdgK-BDzQGfdt0WDkP2QxPtj/w640-h396/relative%20NOPM_%201000%20warehouses,%2040%20virtual%20users.png" width="640"></a></div>
<p><b>Results: vu=10, wh=2000</b></p>
<p>Summary:</p>


<ul style="text-align: left">
<li>There are small regressions in version 18 while NOPM improves is 19 beta1</li>
</ul>
<div class="separator" style="clear: both;text-align: center"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3X2SQitxMnSt9I4kvHyJCm-Jjnk4tp07RFfu2J8aaq_XoVdr5ABj-A_JJIB1g7BrHo4MpkJQSSPdsH1_3V75Nv_960Ba9rnfbW6_VBKC1vsiUJCQ-0_lhuz0th2PrplLR1JqQUlcRCvk8p2WsCfi-Cx6eGEm2BAxd6IR9S7I6PvdIw57EvO0NjgwWP87x/s600/relative%20NOPM_%202000%20warehouses,%2010%20virtual%20users.png" style="margin-left: 1em;margin-right: 1em"><img loading="lazy" decoding="async" border="0" data-original-height="371" data-original-width="600" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3X2SQitxMnSt9I4kvHyJCm-Jjnk4tp07RFfu2J8aaq_XoVdr5ABj-A_JJIB1g7BrHo4MpkJQSSPdsH1_3V75Nv_960Ba9rnfbW6_VBKC1vsiUJCQ-0_lhuz0th2PrplLR1JqQUlcRCvk8p2WsCfi-Cx6eGEm2BAxd6IR9S7I6PvdIw57EvO0NjgwWP87x/w640-h396/relative%20NOPM_%202000%20warehouses,%2010%20virtual%20users.png" width="640"></a></div>
<p><b>Results: vu=20, wh=2000</b></p>
<p>Summary:</p>


<ul style="text-align: left">
<li>There are small regressions in versions 16, 17 and 18 while NOPM improves is 19 beta1</li>
</ul>
<div class="separator" style="clear: both;text-align: center"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqeXwXngBg6LPDxjgODIDS_XTcB4y-KqFNg8VsV3goBCzx6AX5Qf6oPg7p7aOtAjNCtmGrx9C8QidDxiKHoD-YW9Xc5fYsljaC4pcTUUr8RlXLVLTxKH2zzW6jScbPRpRRbgN65BKv-0zrZ4tFjPDlwO-lBgqa_Zcn-uYjCEOzoA5PZt4t15ENvEV1tiVR/s600/relative%20NOPM_%202000%20warehouses,%2020%20virtual%20users.png" style="margin-left: 1em;margin-right: 1em"><img loading="lazy" decoding="async" border="0" data-original-height="371" data-original-width="600" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqeXwXngBg6LPDxjgODIDS_XTcB4y-KqFNg8VsV3goBCzx6AX5Qf6oPg7p7aOtAjNCtmGrx9C8QidDxiKHoD-YW9Xc5fYsljaC4pcTUUr8RlXLVLTxKH2zzW6jScbPRpRRbgN65BKv-0zrZ4tFjPDlwO-lBgqa_Zcn-uYjCEOzoA5PZt4t15ENvEV1tiVR/w640-h396/relative%20NOPM_%202000%20warehouses,%2020%20virtual%20users.png" width="640"></a></div>
<p><b>Results: vu=40, wh=2000</b></p>
<p>Summary:</p>

<ul style="text-align: left">
<li>There are small regressions in versions 16, 17 and 18 while NOPM improves is 19 beta1</li>
<li>There is no result for 18.1 because of a bug in my test scripts</li>
</ul>
<div class="separator" style="clear: both;text-align: center"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWg70aQKWVWL6QuQuy_rXmnB-Y-SN9FR1w7vKbb2TELL50feiivF-T8iYBTvVDEjzbAEqXICOzAJJFn8BJZwvqkoqLze6_76tUm-84luHscpRouH2yHJ7IA-TuMEUMdel0QljbqZ4cqBJIx1qANR3gv5rNvaqlmEAdKMjFnzi8Aiw2faTDuNmxEXEBhrf1/s600/relative%20NOPM_%202000%20warehouses,%2040%20virtual%20users.png" style="margin-left: 1em;margin-right: 1em"><img loading="lazy" decoding="async" border="0" data-original-height="371" data-original-width="600" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWg70aQKWVWL6QuQuy_rXmnB-Y-SN9FR1w7vKbb2TELL50feiivF-T8iYBTvVDEjzbAEqXICOzAJJFn8BJZwvqkoqLze6_76tUm-84luHscpRouH2yHJ7IA-TuMEUMdel0QljbqZ4cqBJIx1qANR3gv5rNvaqlmEAdKMjFnzi8Aiw2faTDuNmxEXEBhrf1/w640-h396/relative%20NOPM_%202000%20warehouses,%2040%20virtual%20users.png" width="640"></a></div>
<p><b>Results: vu=10, wh=4000</b></p>
<p>Summary:</p>


<ul style="text-align: left">
<li>There are small regressions in versions 16, 17 and 18 while NOPM improves is 19 beta1</li>
</ul>
<div class="separator" style="clear: both;text-align: center"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiq5B0YaewnHC3323e_8hMgC7LOa9xZMfTfkQct_EC1Q5YgbiLKDb6SZh7k1lMGzTP_ngq_kGzzIbUaH2AAE9d6ih0bjxXaugGaBQGvBwCjqhQH5XxXeKQFiPS8Itu2XLkvN5yTueNOW1rqnk8IfqCFVgRk5BlUY5BRfcqbVHgE5abHLMLl1rfHy-o2gq7L/s600/relative%20NOPM_%204000%20warehouses,%2010%20virtual%20users.png" style="margin-left: 1em;margin-right: 1em"><img loading="lazy" decoding="async" border="0" data-original-height="371" data-original-width="600" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiq5B0YaewnHC3323e_8hMgC7LOa9xZMfTfkQct_EC1Q5YgbiLKDb6SZh7k1lMGzTP_ngq_kGzzIbUaH2AAE9d6ih0bjxXaugGaBQGvBwCjqhQH5XxXeKQFiPS8Itu2XLkvN5yTueNOW1rqnk8IfqCFVgRk5BlUY5BRfcqbVHgE5abHLMLl1rfHy-o2gq7L/w640-h396/relative%20NOPM_%204000%20warehouses,%2010%20virtual%20users.png" width="640"></a></div>
<p><b>Results: vu=20, wh=4000</b></p>
<p>Summary:</p>


<ul style="text-align: left">
<li>There are small regressions in versions 16, 17 and 18</li>
</ul>
<div class="separator" style="clear: both;text-align: center"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDDMWDhUQC6sr4Oh36Blj8C4I4QQRAQxDv_JpU908j335B4ML-ul062sy4l2x08k2o_GTIfjardMpiFjyF54LRHT_5kxHXCbwwhCMvetGNFFB-fw_4CFtIrGhcbSBvWDJK1BQZaoZXJG0aOs2o4mw0Yj6TnCNmjVMPm5YZJYix1VgqFpP-UAbc1XdDeOxR/s600/relative%20NOPM_%204000%20warehouses,%2020%20virtual%20users.png" style="margin-left: 1em;margin-right: 1em"><img loading="lazy" decoding="async" border="0" data-original-height="371" data-original-width="600" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDDMWDhUQC6sr4Oh36Blj8C4I4QQRAQxDv_JpU908j335B4ML-ul062sy4l2x08k2o_GTIfjardMpiFjyF54LRHT_5kxHXCbwwhCMvetGNFFB-fw_4CFtIrGhcbSBvWDJK1BQZaoZXJG0aOs2o4mw0Yj6TnCNmjVMPm5YZJYix1VgqFpP-UAbc1XdDeOxR/w640-h396/relative%20NOPM_%204000%20warehouses,%2020%20virtual%20users.png" width="640"></a></div>
<p><b>Results: vu=40, wh=4000</b></p>
<p>Summary:</p>


<ul style="text-align: left">
<li>There are small regressions in versions 16, 17 and 18 while NOPM improves is 19 beta1</li>
</ul>
<div class="separator" style="clear: both;text-align: center"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4OGWuarn45EqrYg0CJljnMjxxma62kaQWRQ9vpFBfDM6lkGTMU2zrGSGCJHY35D7L4GTI-Ad6JzOtehh5HfwMPgHOzEzbtKBv2-vWGWbkrFMUCfar5RJm3i9kADaP6XsqCH3NSrpzQ3_EZa_uCDLR_XoGCgxOvLWdaq1FtGOadNhXCtbsLN0XXX7w6ygW/s600/relative%20NOPM_%204000%20warehouses,%2040%20virtual%20users.png" style="margin-left: 1em;margin-right: 1em"><img loading="lazy" decoding="async" border="0" data-original-height="371" data-original-width="600" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4OGWuarn45EqrYg0CJljnMjxxma62kaQWRQ9vpFBfDM6lkGTMU2zrGSGCJHY35D7L4GTI-Ad6JzOtehh5HfwMPgHOzEzbtKBv2-vWGWbkrFMUCfar5RJm3i9kADaP6XsqCH3NSrpzQ3_EZa_uCDLR_XoGCgxOvLWdaq1FtGOadNhXCtbsLN0XXX7w6ygW/w640-h396/relative%20NOPM_%204000%20warehouses,%2040%20virtual%20users.png" width="640"></a></div>
<p></p>
</div>
<div>
<div></div>
</div>
</div>
</div>

<p><a href="https://smalldatum.blogspot.com/2026/06/hammerdb-tproc-c-on-large-server.html">HammerDB tproc-c on a large server, Postgres 14 to 19 beta1</a> appeared first on <a 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><a 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 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><a 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 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><a 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 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><a 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 href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Write-heavy sysbench tests, a large server, modern Postgres and MySQL</title>
      <link rel="alternate" type="text/html" href="https://smalldatum.blogspot.com/2026/06/write-heavy-sysbench-tests-large-server.html" />
      <id>https://smalldatum.blogspot.com/2026/06/write-heavy-sysbench-tests-large-server.html</id>
      <updated>2026-06-12T01:16:04+00:00</updated>
      <author><name>Mark Callaghan</name></author>
      <summary type="html"><![CDATA[<p>This has results for modern Postgres and MySQL using write-heavy tests from sysbench and a large server. I think there are regressions in Postgres that arrive in some of versions 16, 17, 18 and 19 beta1 but I am far from certain and this blog post is just another step in my journey to figure that out.tl;drPostgres suffers a lot from throughput variation while MySQL+InnoDB does notInnoDB gets much better average throughput on 6 of 10 tests, similar throughput one one and then Postgres does better on 3 of 10 testsFor tests from which I provided vmstat and iostat results, Postgres does more write IO per operation. In some cases InnoDB uses more CPU, in other cases it does not.Builds, configuration and hardwareI compiled:Postgres from source for versions 15.17, 16.13, 17.9 and 18.3.MySQL from source for version 8.4.7I used a 48-core server from Hetzneran ax162s with an AMD EPYC 9454P 48-Core Processor with SMT disabled2 Intel D7-P5520 NVMe storage devices with RAID 1 (3.8T each) using ext4128G RAMUbuntu 24.04Configuration files for Postgres:the config file is named conf.diff.cx10a_c32r128 (x10a_c32r128) and is here for versions 15, 16 and 17.for Postgres 18 I used conf.diff.cx10b_c32r128 (x10b_c32r128) which is as close as possible to the Postgres 17 config and uses io_method=syncBenchmarkI used sysbench and my usage is explained here. Normally I run 32 of the 42 microbenchmarks listed in that blog post using tables small enough to be cached by the DBMS. Most test only one type of SQL statement.The tests can be called microbenchmarks. They are very synthetic. But microbenchmarks also make it easy to understand which types of SQL statements have great or lousy performance. Performance testing benefits from a variety of workloads -- both more and less synthetic.But I did things differently here:I only run the write-heavy tests (to save time)The tables are larger than memory and cannot be cachedEach test (microbenchmark) is run for 2 hours when I normally run each for 15 minutesAfter each test a vacuum is doneThe purpose is to search for regressions from new CPU overhead and mutex contention related to MVCC GC (vacuum for Postgres, purge for InnoDB).ResultsI provide charts below with relative QPS. The relative QPS is the following:(QPS for some version) / (QPS for Postgres 15.17)When the relative QPS is > 1 then some version is faster than base version.  When it is < 1 then there might be a regression. When the relative QPS is 1.2 then some version is about 20% faster than base version.The per-test results from vmstat and iostat can help to explain why something is faster or slower because it shows how much HW is used per request, including CPU overhead per operation (cpu/o) and context switches per operation (cs/o) which are often a proxy for mutex contention.Results: writesThe table below has relative QPS for Postgres 16 to 19 and then InnoDB all relative to the throughput for Postgres 15.17. Columns 1 to 4 have results for Postgres and the numbers in yellow highlight the tests where there is a regression in Postgres. For column 5 (MySQL with InnoDB) the numbers in yellow and red indicate tests where InnoDB\'s throughput is less than Postgres. And then the numbers in green indicate tests where InnoDB\'s throughput is much larger than Postgres.Note that when relative QPS (rQPS) is 0.90 then throughput dropped by ~10%.Summary:throughput for Postgres drops after version 15.17. I don\'t know yet whether this is a regression.throughput for InnoDB is much better than Postgres in 6 of 10 tests, similar in one test, and much worse in 3 of 10 tests.The sections that follow this one have more detail on results from the update-index, update-zipf tests and insert tests.Relative to: Postgres 15.17col-1 : Postgres 16.13col-2 : Postgres 17.9col-3 : Postgres 18.3col-4 : Postgres 19 beta1col-5 : MySQL 8.4.7col-1   col-2   col-3   col-4   col-50.94    0.97    0.98    1.02    1.88    update-inlist0.94    0.90    0.88    0.92    1.43    update-index0.91    0.86    0.87    0.92    1.19    update-nonindex0.96    0.99    0.98    0.98    0.71    update-one0.92    0.83    0.81    0.85    0.93    update-zipf0.95    0.93    0.84    0.81    1.71    write-only0.94    0.94    0.90    0.92    1.14    read-write_range=100.95    0.96    0.95    0.95    1.93    read-write_range=1000.89    0.82    0.80    0.84    1.01    delete1.05    1.05    1.01    1.10    0.53    insertResults: update-indexSummary:Postgres suffers from too much varianceAverage throughput is ~1.55X larger for InnoDB than for PostgresPer operation, Postgres does ~1.20X more write IO (KB written) to storage than InnoDBPer operation, InnoDB uses more CPU and does more context switches. While autovacuum was enabled and was likely running during the test, my measurements exclude the manual vacuum done at the end of each test.iostat, vmstat normalized by operation rater/s     rMB/s   w/s     wMB/s   r/o     rKB/o   wKB/o   o/s     dbms35503.0 373.7   58795.7 1345.1  1.375   14.824  53.351  25817   PG 19b133140.6 517.8   53449.6 1735.3  0.827   13.226  44.326  40090   MySQL 8.4.7cs/s    cpu/s   cs/o    cpu/o   dbms176167  14.4     6.824  .000557 PG 19b1661395  41.9    16.498  .001046 MySQL 8.4.7Results: update-zipfSummary:Postgres suffers from too much varianceAverage throughput is ~1.09X larger for InnoDB than for PostgresPer operation, Postgres does ~1.30X more write IO (KB written) to storage than InnoDBPer operation, InnoDB uses more CPU and does more context switches. While autovacuum was enabled and was likely running during the test, my measurements exclude the manual vacuum done at the end of each test.iostat, vmstat normalized by operation rater/s     rMB/s   w/s     wMB/s   r/o     rKB/o   wKB/o   o/s     dbms55595.5 620.7   64264.4 1352.3  0.622   7.110   15.490  89396   PG 19b127405.9 428.2   37465.1 1133.6  0.282   4.508   11.933  97270   MySQL 8.4.7cs/s    cpu/s   cs/o    cpu/o   dbms424392  27.2     4.747  .000304 PG 19b11213054 44.5    12.471  .000458 MySQL 8.4.7Results: insertSummary:Postgres suffers from too much varianceAverage throughput is ~2.06X larger for Postgres than for InnoDBPer operation, Postgres does ~1.67X more write IO (KB written) to storage than InnoDBPer operation, Postgres uses more CPU and does more context switches. This is the opposite of what happens above for update-index and update-zipf.iostat, vmstat normalized by operation rater/s     rMB/s   w/s     wMB/s   r/o     rKB/o   wKB/o   o/s     dbms1615.5  56.0    15321.7 1170.9  0.007   0.242   5.059   237009  PG 19b13.6     0.1     8275.4  340.7   0.000   0.000   3.029   115155  MySQL 8.4.7cs/s    cpu/s   cs/o    cpu/o   dbms1214563 46.0    10.547  .000399 PG 19b1800827  50.5     3.379  .000213 MySQL 8.4.7
</p>
<p><a href="https://smalldatum.blogspot.com/2026/06/write-heavy-sysbench-tests-large-server.html">Write-heavy sysbench tests, a large server, modern Postgres and MySQL</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>This has results for modern Postgres and MySQL using write-heavy tests from sysbench and a large server. I think there are regressions in Postgres that arrive in some of versions 16, 17, 18 and 19 beta1 but I am far from certain and this blog post is just another step in my journey to figure that out.</p>
<p>tl;dr</p>

<ul style="text-align: left">
<li>Postgres suffers a lot from throughput variation while MySQL+InnoDB does not</li>
<li>InnoDB gets much better average throughput on 6 of 10 tests, similar throughput one one and then Postgres does better on 3 of 10 tests</li>
<li>For tests from which I provided vmstat and iostat results, Postgres does more write IO per operation. In some cases InnoDB uses more CPU, in other cases it does not.</li>
</ul>
<div style="background-color: white"><b>Builds, configuration and hardware</b></div>
<div>
<div style="background-color: white">
<div>I compiled:</div>
<div>
<ul>
<li>Postgres from source for versions 15.17, 16.13, 17.9 and 18.3.</li>
<li>MySQL from source for version 8.4.7</li>
</ul>
</div>
<div><span style="font-family: inherit">I used a 48-core server from Hetzner</span></div>
<div>
<ul>
<li>an ax162s with an AMD EPYC 9454P 48-Core Processor with SMT disabled</li>
<li>2 Intel D7-P5520 NVMe storage devices with RAID 1 (3.8T each) using ext4</li>
<li>128G RAM</li>
<li>Ubuntu 24.04</li>
</ul>
<div>
<div><span style="font-family: inherit">Configuration files for Postgres:</span></div>
<div>
<ul>
<li><span style="font-family: inherit">the config file is named conf.diff.cx10a_c32r128 (x10a_c32r128) and is here for versions </span><a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg157_o2nofp/conf.diff.cx10a_c32r128">15</a>, <a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg163_o2nofp/conf.diff.cx10a_c32r128">16</a> and <a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg17beta1_o2nofp/conf.diff.cx10a_c32r128">17</a>.</li>
<li>for Postgres 18 I used <a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c32r128/pg18beta3_o2nofp/conf.diff.cx10b_c32r128" style="font-family: inherit">conf.diff.cx10b_c32r128</a><span style="font-family: inherit"> </span><span style="font-family: inherit">(x10b_c32r128) which is as close as possible to the Postgres 17 config and </span>uses io_method=sync</li>
</ul>
<div>
<div>
<div>
<div><b>Benchmark</b></div>
<div>
<div></div>
<div>I used sysbench and my usage is <a href="http://smalldatum.blogspot.com/2017/02/using-modern-sysbench-to-compare.html">explained here</a>. Normally I run 32 of the 42 microbenchmarks listed in that blog post using tables small enough to be cached by the DBMS. Most test only one type of SQL statement.</div>
<div></div>
<div>The tests can be called microbenchmarks. They are very synthetic. But microbenchmarks also make it easy to understand which types of SQL statements have great or lousy performance. Performance testing benefits from a variety of workloads &mdash; both more and less synthetic.</div>
<div></div>
<div>But I did things differently here:</div>
<div>
<ul style="text-align: left">
<li>I only run the write-heavy tests (to save time)</li>
<li>The tables are larger than memory and cannot be cached</li>
<li>Each test (microbenchmark) is run for 2 hours when I normally run each for 15 minutes</li>
<li>After each test a vacuum is done</li>
</ul>
</div>
</div>
</div>
<div>The purpose is to search for regressions from new CPU overhead and mutex contention related to MVCC GC (vacuum for Postgres, purge for InnoDB).</div>
</div>
<div></div>
<div>
<div style="font-family: inherit"><b>Results</b></div>
<div><span>
<div style="font-family: inherit"></div>
<div style="font-family: inherit">I provide charts below with relative QPS. The relative QPS is the following:</div>
<div style="font-family: inherit">
<div></div>
<blockquote><p>(QPS for some version) / (QPS for Postgres 15.17)</p></blockquote>
</div>
<div><span style="font-family: inherit">When the relative QPS is &gt; 1 then </span><i style="font-family: inherit">some version</i><span style="font-family: inherit"> is faster than <i>base version</i></span><span style="font-family: inherit">.&nbsp; When it is &lt; 1 then there might be a regression. When the relative QPS is 1.2 then <i>some version</i> is about 20% faster than </span><i>base version</i><span style="font-family: inherit">.</span></div>
<div><span style="font-family: inherit"><br></span></div>
<div><span style="font-family: inherit">The per-test results from vmstat and iostat </span><span style="font-family: inherit">can help to explain why something is faster or slower because it shows how much HW is used per request, including CPU overhead per operation (cpu/o) and context switches per operation (cs/o) which are often a proxy for mutex contention.</span></div>
<div><span style="font-family: inherit"><br></span></div>
<div><span style="font-family: inherit"><b>Results: writes</b></span></div>
<div><span style="font-family: inherit"><br></span></div>
<div><span style="font-family: inherit">The table below has relative QPS for Postgres 16 to 19 and then InnoDB all relative to the throughput for Postgres 15.17. Columns 1 to 4 have results for Postgres and the numbers in yellow highlight the tests where there is a regression in Postgres. For column 5 (MySQL with InnoDB) the numbers in yellow and red indicate tests where InnoDB&rsquo;s throughput is less than Postgres. And then the numbers in green indicate tests where InnoDB&rsquo;s throughput is much larger than Postgres.</span></div>
<div><span style="font-family: inherit"><br></span></div>
<div><span style="font-family: inherit">Note that when relative QPS (rQPS) is 0.90 then throughput dropped by ~10%.</span></div>
<div><span style="font-family: inherit"><br>Summary:
<ul style="text-align: left">
<li>throughput for Postgres drops after version 15.17. I don&rsquo;t know yet whether this is a regression.</li>
<li>throughput for InnoDB is much better than Postgres in 6 of 10 tests, similar in one test, and much worse in 3 of 10 tests.</li>
</ul>
<div>The sections that follow this one have more detail on results from the update-index, update-zipf tests and insert tests.</div>
<div></div>
<p></p></span></div>
<div><span>
<div><span style="font-family: courier">Relative to: Postgres 15.17</span></div>
<div><span style="font-family: courier">col-1 : Postgres 16.13</span></div>
<div><span style="font-family: courier">col-2 : Postgres 17.9</span></div>
<div><span style="font-family: courier">col-3 : Postgres 18.3</span></div>
<div><span style="font-family: courier">col-4 : Postgres 19 beta1</span></div>
<div><span style="font-family: courier">col-5 : MySQL 8.4.7</span></div>
<div><span style="font-family: courier"><br></span></div>
<div><span style="font-family: courier">col-1&nbsp; &nbsp;col-2&nbsp; &nbsp;col-3&nbsp; &nbsp;col-4&nbsp; &nbsp;col-5</span></div>
<div><span style="font-family: courier">0.94&nbsp; &nbsp; 0.97&nbsp; &nbsp; 0.98&nbsp; &nbsp; 1.02&nbsp; &nbsp; <span style="background-color: #d9ead3">1.88</span>&nbsp; &nbsp; update-inlist</span></div>
<div><span style="font-family: courier"><span style="background-color: #fff2cc">0.94&nbsp; &nbsp; 0.90&nbsp; &nbsp; 0.88&nbsp; &nbsp; 0.92</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.43</span>&nbsp; &nbsp; update-index</span></div>
<div><span style="font-family: courier"><span style="background-color: #fff2cc">0.91&nbsp; &nbsp; 0.86&nbsp; &nbsp; 0.87&nbsp; &nbsp; 0.92</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.19</span>&nbsp; &nbsp; update-nonindex</span></div>
<div><span style="font-family: courier">0.96&nbsp; &nbsp; 0.99&nbsp; &nbsp; 0.98&nbsp; &nbsp; 0.98&nbsp; &nbsp; <span style="background-color: #f4cccc">0.71</span>&nbsp; &nbsp; update-one</span></div>
<div><span style="font-family: courier"><span style="background-color: #fff2cc">0.92&nbsp; &nbsp; 0.83&nbsp; &nbsp; 0.81&nbsp; &nbsp; 0.85</span>&nbsp; &nbsp; <span style="background-color: #fff2cc">0.93</span>&nbsp; &nbsp; update-zipf</span></div>
<div><span style="font-family: courier"><span style="background-color: #fff2cc">0.95&nbsp; &nbsp; 0.93&nbsp; &nbsp; 0.84&nbsp; &nbsp; 0.81</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.71</span>&nbsp; &nbsp; write-only</span></div>
<div><span style="font-family: courier"><span style="background-color: #fff2cc">0.94&nbsp; &nbsp; 0.94&nbsp; &nbsp; 0.90&nbsp; &nbsp; 0.92</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.14</span>&nbsp; &nbsp; read-write_range=10</span></div>
<div><span style="font-family: courier"><span style="background-color: #fff2cc">0.95&nbsp; &nbsp; 0.96&nbsp; &nbsp; 0.95&nbsp; &nbsp; 0.95</span>&nbsp; &nbsp; <span style="background-color: #d9ead3">1.93</span>&nbsp; &nbsp; read-write_range=100</span></div>
<div><span style="font-family: courier"><span style="background-color: #fff2cc">0.89&nbsp; &nbsp; 0.82&nbsp; &nbsp; 0.80&nbsp; &nbsp; 0.84</span>&nbsp; &nbsp; 1.01&nbsp; &nbsp; delete</span></div>
<div><span style="font-family: courier">1.05&nbsp; &nbsp; 1.05&nbsp; &nbsp; 1.01&nbsp; &nbsp; 1.10&nbsp; &nbsp; <span style="background-color: #f4cccc">0.53</span>&nbsp; &nbsp; insert</span></div>
<div></div>
<div><b>Results: update-index</b></div>
<div></div>
<div>Summary:</div>
<div>
<ul style="text-align: left">
<li>Postgres suffers from too much variance</li>
<li>Average throughput is ~1.55X larger for InnoDB than for Postgres</li>
<li>Per operation, Postgres does ~1.20X more write IO (KB written) to storage than InnoDB</li>
<li>Per operation, InnoDB uses more CPU and does more context switches. While autovacuum was enabled and was likely running during the test, my measurements exclude the manual vacuum done at the end of each test.</li>
</ul>
</div>
<div class="separator" style="clear: both;text-align: center"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4AIH6vKcDIjgnw-Y-8WvPaxGwZO3rBf_kDeSjiQBgtEH8XWXhDWd7Ft2gGXPwc_BXVxgXhSTn6AWmFjtJLB83l4Igbd6TUPAH9-8jf2IZ0gPGR0ixMoZdTqR4a9DJnQjrmltwkxKqDc9RWvtTCLO4N-TmU1BTSZhbh5P1GHESDSi6oN1OvV91UnPJdoxk/s600/update-index_%20Postgres%2019b1%20and%20MySQL%208.4.7.png" style="margin-left: 1em;margin-right: 1em"><img loading="lazy" decoding="async" border="0" data-original-height="371" data-original-width="600" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4AIH6vKcDIjgnw-Y-8WvPaxGwZO3rBf_kDeSjiQBgtEH8XWXhDWd7Ft2gGXPwc_BXVxgXhSTn6AWmFjtJLB83l4Igbd6TUPAH9-8jf2IZ0gPGR0ixMoZdTqR4a9DJnQjrmltwkxKqDc9RWvtTCLO4N-TmU1BTSZhbh5P1GHESDSi6oN1OvV91UnPJdoxk/w640-h396/update-index_%20Postgres%2019b1%20and%20MySQL%208.4.7.png" width="640"></a></div>
<div>
<div><span style="font-family: courier;font-size: x-small">iostat, vmstat normalized by operation rate</span></div>
<div><span style="font-family: courier;font-size: x-small">r/s&nbsp; &nbsp; &nbsp;rMB/s&nbsp; &nbsp;w/s&nbsp; &nbsp; &nbsp;wMB/s&nbsp; &nbsp;r/o&nbsp; &nbsp; &nbsp;rKB/o&nbsp; &nbsp;wKB/o&nbsp; &nbsp;o/s&nbsp; &nbsp; &nbsp;dbms</span></div>
<div><span style="font-family: courier;font-size: x-small">35503.0 373.7&nbsp; &nbsp;58795.7 1345.1&nbsp; 1.375&nbsp; &nbsp;14.824&nbsp; <span style="background-color: #fff2cc">53.351</span>&nbsp; <span style="background-color: #fff2cc">25817</span>&nbsp; &nbsp;PG 19b1</span></div>
<div><span style="font-family: courier;font-size: x-small">33140.6 517.8&nbsp; &nbsp;53449.6 1735.3&nbsp; 0.827&nbsp; &nbsp;13.226&nbsp; <span style="background-color: #d9ead3">44.326</span>&nbsp; <span style="background-color: #d9ead3">40090</span>&nbsp; &nbsp;MySQL 8.4.7</span></div>
<div><span style="font-family: courier;font-size: x-small"><br></span></div>
<div><span style="font-family: courier;font-size: x-small">cs/s&nbsp; &nbsp; cpu/s&nbsp; &nbsp;cs/o&nbsp; &nbsp; cpu/o&nbsp; &nbsp;dbms</span></div>
<div><span style="font-family: courier;font-size: x-small">176167&nbsp; 14.4&nbsp; &nbsp; &nbsp;<span style="background-color: #d9ead3">6.824</span>&nbsp;&nbsp;<span style="background-color: #d9ead3">.000557</span> PG 19b1</span></div>
<div><span style="font-family: courier;font-size: x-small">661395&nbsp; 41.9&nbsp; &nbsp; <span style="background-color: #fff2cc">16.498</span>&nbsp; <span style="background-color: #fff2cc">.001046</span> MySQL 8.4.7</span></div>
</div>
<div><b><br></b></div>
<div><b>Results: update-zipf</b></div>
<div></div>
<div>
<div>Summary:</div>
<div>
<ul>
<li>Postgres suffers from too much variance</li>
<li>Average throughput is ~1.09X larger for InnoDB than for Postgres</li>
<li>Per operation, Postgres does ~1.30X more write IO (KB written) to storage than InnoDB</li>
<li>Per operation, InnoDB uses more CPU and does more context switches. While autovacuum was enabled and was likely running during the test, my measurements exclude the manual vacuum done at the end of each test.</li>
</ul>
</div>
</div>
<div class="separator" style="clear: both;text-align: center"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBpMvt9NeKpNXTBkF99Qdgjhy5cA4QFu__1yTl5KMkGpLKxlPUJ-WnNJAHDQI72w1-ySz3b-tmL6j2P5G-ogs0WNl9ZA0hezzA9CyxRSpigd87RoU1rgiCgpm1xjUEpNitGyq6eXGPUrECd2P7nWoQH_l504xfdelFU2bKb3yXJ18WpRLogreQXJCGxc6e/s600/update-zipf_%20Postgres%2019b1%20and%20MySQL%208.4.7.png" style="margin-left: 1em;margin-right: 1em"><img loading="lazy" decoding="async" border="0" data-original-height="371" data-original-width="600" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBpMvt9NeKpNXTBkF99Qdgjhy5cA4QFu__1yTl5KMkGpLKxlPUJ-WnNJAHDQI72w1-ySz3b-tmL6j2P5G-ogs0WNl9ZA0hezzA9CyxRSpigd87RoU1rgiCgpm1xjUEpNitGyq6eXGPUrECd2P7nWoQH_l504xfdelFU2bKb3yXJ18WpRLogreQXJCGxc6e/w640-h396/update-zipf_%20Postgres%2019b1%20and%20MySQL%208.4.7.png" width="640"></a></div>
<div>
<div><span style="font-family: courier;font-size: x-small">iostat, vmstat normalized by operation rate</span></div>
<div><span style="font-family: courier;font-size: x-small">r/s&nbsp; &nbsp; &nbsp;rMB/s&nbsp; &nbsp;w/s&nbsp; &nbsp; &nbsp;wMB/s&nbsp; &nbsp;r/o&nbsp; &nbsp; &nbsp;rKB/o&nbsp; &nbsp;wKB/o&nbsp; &nbsp;o/s&nbsp; &nbsp; &nbsp;dbms</span></div>
<div><span style="font-family: courier;font-size: x-small">55595.5 620.7&nbsp; &nbsp;64264.4 1352.3&nbsp; 0.622&nbsp; &nbsp;7.110&nbsp; &nbsp;<span style="background-color: #fff2cc">15.490</span>&nbsp; <span style="background-color: #fff2cc">89396</span>&nbsp; &nbsp;PG 19b1</span></div>
<div><span style="font-family: courier;font-size: x-small">27405.9 428.2&nbsp; &nbsp;37465.1 1133.6&nbsp; 0.282&nbsp; &nbsp;4.508&nbsp; &nbsp;<span style="background-color: #d9ead3">11.933</span>&nbsp; <span style="background-color: #d9ead3">97270</span>&nbsp; &nbsp;MySQL 8.4.7</span></div>
<div><span style="font-family: courier;font-size: x-small"><br></span></div>
<div><span style="font-family: courier;font-size: x-small">cs/s&nbsp; &nbsp; cpu/s&nbsp; &nbsp;cs/o&nbsp; &nbsp; cpu/o&nbsp; &nbsp;dbms</span></div>
<div><span style="font-family: courier;font-size: x-small">424392&nbsp; 27.2&nbsp; &nbsp; &nbsp;<span style="background-color: #d9ead3">4.747</span>&nbsp;&nbsp;<span style="background-color: #d9ead3">.000304</span> PG 19b1</span></div>
<div><span style="font-family: courier;font-size: x-small">1213054 44.5&nbsp; &nbsp; <span style="background-color: #fff2cc">12.471</span>&nbsp; <span style="background-color: #fff2cc">.000458</span> MySQL 8.4.7</span></div>
</div>
<div><b><br></b></div>
<div><b>Results: insert</b></div>
<p></p></span></div>
<p></p></span></div>
</div>
<div></div>
<div>
<div>Summary:</div>
<div>
<ul>
<li>Postgres suffers from too much variance</li>
<li>Average throughput is ~2.06X larger for Postgres than for InnoDB</li>
<li>Per operation, Postgres does ~1.67X more write IO (KB written) to storage than InnoDB</li>
<li>Per operation, Postgres uses more CPU and does more context switches. This is the opposite of what happens above for update-index and update-zipf.</li>
</ul>
</div>
</div>
<div class="separator" style="clear: both;text-align: center"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWbY3tiGRuHceEWnN5sIEPuufQGA49wSYP1MmCAb1lGL79Jh0uvRyv2bGaQZo2uDTkWTfLVIONEObrQajeUZ1qxJPYZ6EnjMI9Nkb5XV3x6w_rrgNoqA9qtVDNsW09QcM89pht9VVBklAWeQPmfRt6zZuYU_YoXzo7VOINjy9gh5-b0GjN4WrW5AjaV3W-/s600/insert_%20Postgres%2019b1%20and%20MySQL%208.4.7.png" style="margin-left: 1em;margin-right: 1em"><img loading="lazy" decoding="async" border="0" data-original-height="371" data-original-width="600" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWbY3tiGRuHceEWnN5sIEPuufQGA49wSYP1MmCAb1lGL79Jh0uvRyv2bGaQZo2uDTkWTfLVIONEObrQajeUZ1qxJPYZ6EnjMI9Nkb5XV3x6w_rrgNoqA9qtVDNsW09QcM89pht9VVBklAWeQPmfRt6zZuYU_YoXzo7VOINjy9gh5-b0GjN4WrW5AjaV3W-/w640-h396/insert_%20Postgres%2019b1%20and%20MySQL%208.4.7.png" width="640"></a></div>
<div></div>
<div>
<div><span style="font-family: courier;font-size: x-small">iostat, vmstat normalized by operation rate</span></div>
<div><span style="font-family: courier;font-size: x-small">r/s&nbsp; &nbsp; &nbsp;rMB/s&nbsp; &nbsp;w/s&nbsp; &nbsp; &nbsp;wMB/s&nbsp; &nbsp;r/o&nbsp; &nbsp; &nbsp;rKB/o&nbsp; &nbsp;wKB/o&nbsp; &nbsp;o/s&nbsp; &nbsp; &nbsp;dbms</span></div>
<div><span style="font-family: courier;font-size: x-small">1615.5&nbsp; 56.0&nbsp; &nbsp; 15321.7 1170.9&nbsp; 0.007&nbsp; &nbsp;0.242&nbsp; &nbsp;<span style="background-color: #fff2cc">5.059</span>&nbsp; &nbsp;<span style="background-color: #d9ead3">237009</span>&nbsp; PG 19b1</span></div>
<div><span style="font-family: courier;font-size: x-small">3.6&nbsp; &nbsp; &nbsp;0.1&nbsp; &nbsp; &nbsp;8275.4&nbsp; 340.7&nbsp; &nbsp;0.000&nbsp; &nbsp;0.000&nbsp; &nbsp;<span style="background-color: #d9ead3">3.029</span>&nbsp; &nbsp;<span style="background-color: #fff2cc">115155</span>&nbsp; MySQL 8.4.7</span></div>
<div><span style="font-family: courier;font-size: x-small"><br></span></div>
<div><span style="font-family: courier;font-size: x-small">cs/s&nbsp; &nbsp; cpu/s&nbsp; &nbsp;cs/o&nbsp; &nbsp; cpu/o&nbsp; &nbsp;dbms</span></div>
<div><span style="font-family: courier;font-size: x-small">1214563 46.0&nbsp; &nbsp; <span style="background-color: #fff2cc">10.547</span>&nbsp; <span style="background-color: #fff2cc">.000399</span> PG 19b1</span></div>
<div><span style="font-family: courier;font-size: x-small">800827&nbsp; 50.5&nbsp; &nbsp; &nbsp;<span style="background-color: #d9ead3">3.379</span>&nbsp; <span style="background-color: #d9ead3">.000213</span> MySQL 8.4.7</span></div>
</div>
<div></div>
<div></div>
</div>
</div>
</div>
</div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>

<p><a href="https://smalldatum.blogspot.com/2026/06/write-heavy-sysbench-tests-large-server.html">Write-heavy sysbench tests, a large server, modern Postgres and MySQL</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>The insert benchmark on a small server, cached workload : Postgres 19 beta1</title>
      <link rel="alternate" type="text/html" href="https://smalldatum.blogspot.com/2026/06/the-insert-benchmark-on-small-server.html" />
      <id>https://smalldatum.blogspot.com/2026/06/the-insert-benchmark-on-small-server.html</id>
      <updated>2026-06-11T17:05:28+00:00</updated>
      <author><name>Mark Callaghan</name></author>
      <summary type="html"><![CDATA[<p>This has results for Postgres versions 19 beta1, 18.4 and 17.10 with the Insert Benchmark on a small server using a cached and CPU-bound workload.Postgres continues to be boring in a good way. It is hard to find performance regressions. tl;drI don\'t see regressions here in 19 beta1I see some improvements here in 19 beta1index create (l.x) is faster but the step is short-running so I don\'t assume much from thisthe write-heavy steps (l.i1, l.i2) are faster and CPU overhead is lower in 19 beta1, I hope to explain why the CPU overhead is lower, but that waits for another day.Builds, configuration and hardwareI compiled Postgres from source using -O2 -fno-omit-frame-pointer for versions 19 beta1, 18.4 and 17.10.The server is an Beelink SER7 with a Ryzen 7 7840HS CPU with 8 cores and AMD SMT disabled, 32G of RAM. Storage is one SSD for the OS and an NVMe SSD for the database using ext-4 with discard enabled. The OS is Ubuntu 24.04.For 17.10 the config file is named conf.diff.cx10a_c8r32 (cx10a) and is here.For Postgres 18 and 19 the config file is conf.diff.cx10b_c8r32 (cx10b) which is as similar as possible to the config for version 17.The BenchmarkThe benchmark is explained here and is run with 1 client.The point query (qp100, qp500, qp1000) and range query (qr100, qr500, qr1000) steps are run for 3600 seconds each.The benchmark steps are:l.i0insert 30M rows per table in PK order. The table has a PK index but no secondary indexes. There is one connection per client.l.xcreate 3 secondary indexes per table. There is one connection per client.l.i1use 2 connections/client. One inserts 40M rows per table and the other does deletes at the same rate as the inserts. Each transaction modifies 50 rows (big transactions). This step is run for a fixed number of inserts, so the run time varies depending on the insert rate.l.i2like l.i1 but each transaction modifies 5 rows (small transactions) and 10M rows are inserted and deleted per table.Wait for S seconds after the step finishes to reduce variance during the read-write benchmark steps that follow. The value of S is a function of the table size.qr100use 3 connections/client. One does range queries and performance is reported for this. The second does does 100 inserts/s and the third does 100 deletes/s. The second and third are less busy than the first. The range queries use covering secondary indexes. If the target insert rate is not sustained then that is considered to be an SLA failure. If the target insert rate is sustained then the step does the same number of inserts for all systems tested. This step is frequently not IO-bound for the IO-bound workload.qp100like qr100 except uses point queries on the PK indexqr500like qr100 but the insert and delete rates are increased from 100/s to 500/sqp500like qp100 but the insert and delete rates are increased from 100/s to 500/sqr1000like qr100 but the insert and delete rates are increased from 100/s to 1000/sqp1000like qp100 but the insert and delete rates are increased from 100/s to 1000/sResultsThe performance summary with charts is here.This table lists relative QPS per benchmark step and relative QPS is:    (QPS for my version / QPS for Postgres 17.10)The background in the table cells is blue for big improvements and yellow for regressions. There are no regressions here. The index create (l.x) step is much faster in 19.10. I usually ignore results on this step but I am curious if something was done in 19.10 to improve index create. But this step takes between 1 and 2 minutes and I am reluctant to assume too much from a short running step.For the write-heavy steps (l.i1, l.i2)there are small improvements in 18.4there are large improvements in 19 beta1. The CPU overhead is lower in 19 beta1 compared to 17.10, ~15% lower for l.i1 and ~10% lower for l.i2. Hopefully I can explain why. But the lower CPU overhead might explain the improved performance in 19 beta1. Some of the metrics from iostat and vmstat are here.dbmsl.i0l.xl.i1l.i2qr100qp100qr500qp500qr1000qp100017.101.001.001.001.001.001.001.001.001.001.0018.41.001.031.021.070.991.001.001.001.011.0019 beta11.011.161.231.220.991.000.990.991.001.00</p>
<p><a href="https://smalldatum.blogspot.com/2026/06/the-insert-benchmark-on-small-server.html">The insert benchmark on a small server, cached workload : Postgres 19 beta1</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>This has results for Postgres versions 19 beta1, 18.4 and 17.10 with the <a href="https://smalldatum.blogspot.com/2023/12/updates-for-insert-benchmark-december.html">Insert Benchmark</a> on a small server using a cached and CPU-bound workload.</p>

<p>Postgres continues to be boring in a good way. It is hard to find performance regressions.</p>
<p>&nbsp;tl;dr</p>

<ul style="text-align: left">
<li>I don&rsquo;t see regressions here in 19 beta1</li>
<li>I see some improvements here in 19 beta1</li>
<ul>
<li>index create (l.x) is faster but the step is short-running so I don&rsquo;t assume much from this</li>
<li>the write-heavy steps (l.i1, l.i2) are faster and CPU overhead is lower in 19 beta1, I hope to explain why the CPU overhead is lower, but that waits for another day.</li>
</ul>
</ul>
<div><b>Builds, configuration and hardware</b></div>
<div>
<div></div>
<div>I compiled Postgres from source using&nbsp;<i>-O2 -fno-omit-frame-pointer</i>&nbsp;for versions 19 beta1, 18.4 and 17.10.</div>
<div>The server is an Beelink SER7 with a Ryzen 7 7840HS CPU with 8 cores and AMD SMT disabled, 32G of RAM. Storage is one SSD for the OS and an NVMe SSD for the database using ext-4 with discard enabled. The OS is Ubuntu 24.04.</div>
<div></div>
<div>For 17.10 the config file is named conf.diff.cx10a_c8r32 (cx10a) and&nbsp;<a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c8r32/pg172_o2nofp/conf.diff.cx10a_c8r32">is here</a>.</div>

<div>For Postgres 18 and 19&nbsp;the config file is&nbsp;<a href="https://github.com/mdcallag/mytools/blob/master/bench/conf/arc/oct24/c8r32/pg18_o2nofp/conf.diff.cx10b_c8r32">conf.diff.cx10b_c8r32</a>&nbsp;(cx10b) which is as similar as possible to the config for version 17.</div>
</div>
<div></div>
<div>
<div><b>The Benchmark</b></div>
<div>
<div></div>
<div>The benchmark is <a href="https://smalldatum.blogspot.com/2023/12/updates-for-insert-benchmark-december.html">explained here</a> and is run with 1 client.</div>
<div></div>
<div>
<div>The point query (qp100, qp500, qp1000) and range query (qr100, qr500, qr1000) steps are run for 3600 seconds each.</div>
</div>
<div></div>
<div>The benchmark steps are:</div>
<div>
<div>
<ul>
<li>l.i0</li>
<ul>
<li>insert 30M rows per table in PK order. The table has a PK index but no secondary indexes. There is one connection per client.</li>
</ul>
<li>l.x</li>
<ul>
<li>create 3 secondary indexes per table. There is one connection per client.</li>
</ul>
<li>l.i1</li>
<ul>
<li>use 2 connections/client. One inserts 40M rows per table and the other does deletes at the same rate as the inserts. Each transaction modifies 50 rows (big transactions). This step is run for a fixed number of inserts, so the run time varies depending on the insert rate.</li>
</ul>
<li>l.i2</li>
<ul>
<li>like l.i1 but each transaction modifies 5 rows (small transactions) and 10M rows are inserted and deleted per table.</li>
<li>Wait for S seconds after the step finishes to reduce variance during the read-write benchmark steps that follow. The value of S is a function of the table size.</li>
</ul>
<li>qr100</li>
<ul>
<li>use 3 connections/client. One does range queries and performance is reported for this. The second does does 100 inserts/s and the third does 100 deletes/s. The second and third are less busy than the first. The range queries use covering secondary indexes. If the target insert rate is not sustained then that is considered to be an SLA failure. If the target insert rate is sustained then the step does the same number of inserts for all systems tested. This step is frequently not IO-bound for the IO-bound workload.</li>
</ul>
<li>qp100</li>
<ul>
<li>like qr100 except uses point queries on the PK index</li>
</ul>
<li>qr500</li>
<ul>
<li>like qr100 but the insert and delete rates are increased from 100/s to 500/s</li>
</ul>
<li>qp500</li>
<ul>
<li>like qp100 but the insert and delete rates are increased from 100/s to 500/s</li>
</ul>
<li>qr1000</li>
<ul>
<li>like qr100 but the insert and delete rates are increased from 100/s to 1000/s</li>
</ul>
<li>qp1000</li>
<ul>
<li>like qp100 but the insert and delete rates are increased from 100/s to 1000/s</li>
</ul>
</ul>
<div><b>Results</b></div>
</div>
</div>
</div>
</div>
<div></div>
<div>The performance summary with charts <a href="https://mdcallag.github.io/reports/jun26.ib.pn52.mem.30m.50m.3600s.1u.pg/all.html#summary">is here</a>.</div>
<div></div>
<div>This table lists relative QPS per benchmark step and relative QPS is:<br>&nbsp; &nbsp; (QPS for my version / QPS for Postgres 17.10)
<p>The background in the table cells is blue for big improvements and yellow for regressions. There are no regressions here.&nbsp;</p></div>
<div></div>
<div>The index create (l.x) step is much faster in 19.10. I usually ignore results on this step but I am curious if something was done in 19.10 to improve index create. But this step takes between 1 and 2 minutes and I am reluctant to assume too much from a short running step.</div>
<div></div>
<div>For the write-heavy steps (l.i1, l.i2)</div>
<div>
<ul style="text-align: left">
<li>there are small improvements in 18.4</li>
<li>there are large improvements in 19 beta1. The CPU overhead is lower in 19 beta1 compared to 17.10, ~15% lower for l.i1 and ~10% lower for l.i2. Hopefully I can explain why. But the lower CPU overhead might explain the improved performance in 19 beta1. Some of the metrics from iostat and vmstat <a href="https://mdcallag.github.io/reports/jun26.ib.pn52.mem.30m.50m.3600s.1u.pg/all.html#l.i1.metrics">are here</a>.</li>
</ul>
</div>
<div>
<table border="1" cellpadding="8" style="color: black">
<tbody>
<tr>
<th><span style="font-size: x-small">dbms</span></th>
<th><span style="font-size: x-small">l.i0</span></th>
<th><span style="font-size: x-small">l.x</span></th>
<th><span style="font-size: x-small">l.i1</span></th>
<th><span style="font-size: x-small">l.i2</span></th>
<th><span style="font-size: x-small">qr100</span></th>
<th><span style="font-size: x-small">qp100</span></th>
<th><span style="font-size: x-small">qr500</span></th>
<th><span style="font-size: x-small">qp500</span></th>
<th><span style="font-size: x-small">qr1000</span></th>
<th><span style="font-size: x-small">qp1000</span></th>
</tr>
<tr>
<td style="text-align: right"><span style="font-size: x-small">17.10</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
</tr>
<tr>
<td style="text-align: right"><span style="font-size: x-small">18.4</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.03</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.02</span></td>
<td id="chi" style="background-color: #81fff9;text-align: right"><span style="font-size: x-small">1.07</span></td>
<td style="text-align: right"><span style="font-size: x-small">0.99</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.01</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
</tr>
<tr>
<td style="text-align: right"><span style="font-size: x-small">19 beta1</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.01</span></td>
<td id="chi" style="background-color: #81fff9;text-align: right"><span style="font-size: x-small">1.16</span></td>
<td id="chi" style="background-color: #81fff9;text-align: right"><span style="font-size: x-small">1.23</span></td>
<td id="chi" style="background-color: #81fff9;text-align: right"><span style="font-size: x-small">1.22</span></td>
<td style="text-align: right"><span style="font-size: x-small">0.99</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">0.99</span></td>
<td style="text-align: right"><span style="font-size: x-small">0.99</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00</span></td>
<td style="text-align: right"><span style="font-size: x-small">1.00<br></span></td>
</tr>
</tbody>
</table>
</div>

<p><a href="https://smalldatum.blogspot.com/2026/06/the-insert-benchmark-on-small-server.html">The insert benchmark on a small server, cached workload : Postgres 19 beta1</a> appeared first on <a 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><a 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 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><a 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 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><a 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 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><a 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 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><a 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 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><a 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 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><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/">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 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><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/">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 href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>File and post data confusion in PHP</title>
      <link rel="alternate" type="text/html" href="https://www.sjoerdlangkemper.nl/2026/06/10/files-post-confusion-in-laminas/" />
      <id>https://www.sjoerdlangkemper.nl/2026/06/10/files-post-confusion-in-laminas/</id>
      <updated>2026-06-10T05:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>PHP has several superglobal variables which contain values from the request or the environment. These differ in whether they contain trustworthy data or not:</p>
<p><a href="https://www.sjoerdlangkemper.nl/2026/06/10/files-post-confusion-in-laminas/">File and post data confusion in PHP</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>PHP has several superglobal variables which contain values from the request or the environment. These differ in whether they contain trustworthy data or not:</p>

<p><a href="https://www.sjoerdlangkemper.nl/2026/06/10/files-post-confusion-in-laminas/">File and post data confusion in PHP</a> appeared first on <a 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><a 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 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><a 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 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><a 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 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><a 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 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><a 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 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><a 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 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><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://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 loading="lazy" decoding="async" 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 loading="lazy" decoding="async" 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 loading="lazy" decoding="async" 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 loading="lazy" decoding="async" 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><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://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><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://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><img loading="lazy" decoding="async" 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 loading="lazy" decoding="async" 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><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://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><a 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 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><a 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 href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>SpacemiT K3 is a compelling RISC-V AI CPU, but difficult to buy</title>
      <link rel="alternate" type="text/html" href="https://optimizedbyotto.com/post/buying-spacemit-k3-risc-v-ai-cpu/" />
      <id>https://optimizedbyotto.com/post/buying-spacemit-k3-risc-v-ai-cpu/</id>
      <updated>2026-06-09T00:00:00+00:00</updated>
      <author><name></name></author>
      <summary type="html"><![CDATA[<p>The RISC-V CPU architecture has been gaining a lot of popularity since it launched in 2014, and now that the industry is standardizing on the RVA23 level that includes vector support as a mandatory extension, we are likely to see a lot more edge- and IoT devices with the ability to run local LLMs at reasonable speed, and most importantly at very compelling prices.<br />
SpacemiT is a Chinese RISC-V CPU manufacturer that launched on May 11th, 2026, their long-anticipated next-gen RISC-V AI chip K3. It is among the earliest RISC-V CPUs that adhere to the RVA23 standard and performance-wise it is quite capable, providing 130 KDMIPS general computing power, 60 TOPS on INT4 which translates to about 15 tokens per second when running a 30 billion parameter large language model.<br />
The aspect that really makes it stand out is:</p>
<p>the RISC-V CPU architecture is open source,<br />
the price point is within reach of home and small business users and<br />
the overall feature set makes it an ideal platform to build local and offline AI systems.</p>
<p>SpacemiT also develops their own Debian-based Linux distribution Bianbu OS, and seems to have collaboration going on with the wider community. Their community site seems active, and they also have a dedicated X account @spacemit_riscv and Reddit account r/spacemit_riscv posting relevant progress info on Linux kernel upstreaming activities. The X account is also responsive, as evidenced by its replies to my questions.<br />
Canonical lists the SpacemiT K3 pico-ITX and K3 CoM260 Kit on its official Ubuntu for RISC-V partner-built hardware page, which strengthens the perception that upstream Linux support is being taken seriously. The SpacemiT folks also gave an interesting talk at the 2026 Ubuntu Summit that includes a peek into their roadmap with future K3, K7 and K9 models.<br />
For technical details, see SpacemiT’s K3 pico-ITX documentation, the Jetson Orin Nano-compatible K3 CoM260 board documentation and documentation of the K3 processor itself.</p>
<p>Comparing the resellers<br />
SpacemiT does not sell anything directly to consumers. Instead you need to buy a board that includes the K3 chip from an integrator. Currently the main resellers are:</p>
<p>Milk-V<br />
Sipeed<br />
Banana Pi<br />
Firefly<br />
DeepComputing</p>
<p>All of the above are Chinese companies that ship to customers both inside and outside China. DeepComputing stands out as the only one that actually has done real integration and ships the K3 on a custom board, while the others simply resell the SpacemiT-produced K3 pico-ITX and K3 CoM260 Kit.<br />
Milk-V<br />
Milk-V is a RISC-V specialized integrator, as the name already implies. They sell the K3 under the name Jupiter2. Of all the K3 pico-ITX reseller product pages, the Jupiter2 presentation is the nicest and most detailed. Unfortunately their order page at arace.tech only states that it is a “pre-order” with no information about shipping schedule, taxes, or other details like what SSD is included (if any). Based on the pictures it does ship with a Milk-V branded case. The 32 GB RAM lists at 504 EUR, which is a very reasonable price. The @MilkV_Official account on X recently promoted the K3.<br />
Documentation and support<br />
As of this writing, the Milk-V Jupiter2 documentation site is just a stub and has no actual content, and only two links to the SpacemiT K3 documentation site. For support there is a web forum with a dedicated Jupiter2 section. There is also a Matrix space, but unlike their other products, there is no dedicated Jupiter (neither v1 nor v2) channel.<br />
Community size and open source involvement<br />
At least one prior Milk-V product was certified by Canonical, which indicates there is some collaboration in progress. Canonical also lists the Milk-V Titan on its official Ubuntu for RISC-V partner-built hardware page.<br />
Sipeed<br />
The Sipeed K3 announcement is well written (in English) with all the relevant details and links to additional PDF manuals. However, their main page at sipeed.com says nothing about the K3, so one must know the subpage URL to access it. They offer both the K3 CoM260 kit compatible with Jetson Orin Nano carrier boards, and the stand-alone K3 pico-ITX-sized motherboard. The CoM260 kit is only 10 USD cheaper than the full pico-ITX motherboard, so choosing the latter is a no-brainer if starting from scratch. The pico-ITX model with 32 GB DDR5 RAM sells for 639 USD. The product page does not mention anything about hard disk size, so you don’t really know exactly what you will be getting if placing an order. There is no indication about case, Wi-Fi antennas or power supply either, so most likely they are not included.<br />
Their store.sipeed.com website does not work at all, and their Taobao and AliExpress stores are not public and only accessible to registered users. The order page also says nothing about shipping time, delivery time, or taxes. The X account @SipeedIO is active and recently posted pictures of shipments in progress.<br />
Documentation and support<br />
The main documentation wiki does not yet have any K3 content at the time of writing. There is a Discord channel for general RISC-V discussion, and their MaixHub also has a discussion board, but I didn’t find anything K3-specific.<br />
Community size and open source involvement<br />
Sipeed has had at least one of their previous devices certified by Canonical, which indicates they are active in the community.<br />
Note that the other RISC-V company SiFive that also has had hardware certified and officially supported by Canonical is a different company, despite the very similar name.<br />
Banana Pi<br />
Banana Pi announced that they offer both the K3 CoM260 kit and the K3 pico-ITX motherboard version. Their product page for the K3 confusingly shows a MediaTek product in the page banner rather than the SpacemiT K3. Based on the product description and the fact they renamed the product as BPI-SM10, it seems to ship with some carrier board. The product pictures look identical to the SpacemiT documentation and there is no picture of the carrier board, and details are very sparse. The pico-ITX version with 8 GB RAM and 128 GB SSD sells for 293 USD and the CoM260 developer kit with the same specs sells for 287 USD and the 32 GB RAM with 128 GB SSD model sells for 595 USD. The shop page shows only five orders so far and items are currently out of stock. As there was no 32 GB RAM version of the pico-ITX available at all, this isn’t an option for me as I want to run 30B parameter models that need the larger memory version.<br />
Of all of these resellers, the Banana Pi website seems the most outdated. It does not have a search feature, it is not mobile-friendly, pictures can’t be pinched to zoom in and so forth. Product names are also almost all identical, and as the product listings only show the beginning of the product name, figuring out what product is what requires extra effort that just makes the online purchase experience plain bad.<br />
Documentation and support<br />
I was only able to find the documentation page for the CoM260 kit, but none for the pico-ITX version. For support there is a forum, but the category list does not show any section for K3, and the forum search prohibits using the search term “k3” as too short.<br />
Community size and open source involvement<br />
Banana Pi has a long history in the ARM single-board computer market, but their presence in the RISC-V ecosystem is still growing. Their X account @sinovoip has posted only once about the K3 and otherwise promotes their ARM boards. However, their community culture page does express a commitment to open hardware in general, but there is no visible K3-specific community activity.<br />
Firefly<br />
Firefly’s K3 product page is comprehensive. Based on the details, they do not offer the K3 pico-ITX variant at all, but only the K3 CoM260 board inside the AIBOX-K3 Firefly RISC-V Edge Mini PC product. This is a feature-complete offering with a Jetson Orin Nano carrier board and case. The AIBOX-K3 with 32 GB RAM and 128 GB SSD in a case sells for 689 USD in their own Firefly.store. Unfortunately it only has HDMI and there is no USB-C with DisplayPort support, which is a deal-breaker for me personally.<br />
Interestingly, Firefly also offers rack-mounted servers with K3 as the CPU.<br />
Documentation and support<br />
The wiki link on the product page is broken. The Firefly wiki does have a section for the AIBOX-K3, but it too has a broken link. It seems that as of the time of writing, there is no wiki section for this product yet.<br />
For support there is a web forum, which does have at least one K3 thread covering guides such as Hermes Agent installation, though broader K3-specific sections are still sparse.<br />
Community size and open source involvement<br />
Firefly’s X account @TeeFirefly has had no posts since 2024, and their GitLab/T-Firefly shows mostly 2024 activity, with only one repository updated in 2025 and nothing in 2026. Historically they have built a moderate community around their ARM-based Rockchip boards, with active forums and wiki contributions for those product lines. Their RISC-V K3 offerings are newer, and likely need a lot more polish to be attractive products overall.<br />
DeepComputing<br />
Last, but certainly not least, is the laptop manufacturer DeepComputing that offers a Framework laptop compatible motherboard with the SpacemiT K3 chip. They also sell the plain motherboard, or with the Cooler Master case, which allows one to easily connect it to an external monitor and keyboard and use it as a desktop computer. The plain board with 32 GB RAM and no SSD sells for about 882 EUR. Shipping of the first batch is expected to start by end of June 2026. Their X account @DeepComputingio promotes this DC-ROMA RISC-V Mainboard III as their flagship product, so they seem to put a lot of effort into it.<br />
The overall product design and packaging seems good. Of all the K3 resellers and integrators that I was able to find, DeepComputing is the only one that actually designs their own boards with the K3 processor, while all the other vendors above are simply reselling the vanilla K3 boards with or without a case.<br />
After reviewing all these options I decided to buy the DC-ROMA RISC-V Mainboard III for Framework Laptop 13 with 32 GB RAM, 1 TB SSD and the Cooler Master case, totalling about 1100 EUR.<br />
Documentation and support<br />
DeepComputing maintains product information for their RISC-V hardware at github.com/DC-DeepComputing/Framework, with documentation of the newest Mainboard III (FML13V05) still being finalized ahead of the first batch shipment. They provide community support through Discord and web forum, although the latter has very little activity.<br />
Community size and open source involvement<br />
DeepComputing has established itself as a pioneer in RISC-V laptops, beginning with the DC-ROMA. I have seen their stand at FOSDEM, which shows they are genuinely active in the open source community. Canonical lists DeepComputing’s first mainboard / FML13V01 on its official Ubuntu for RISC-V partner-built hardware page, and it seems likely that they will continue to collaborate with Canonical with the new model once it ships. While the underlying Linux enablement depends on SpacemiT’s upstream efforts, DeepComputing’s involvement helps bridge the gap between reference hardware and consumer-ready products.</p>
<p>Conclusion<br />
After weighing all the options, I ended up placing an order with DeepComputing for their custom K3 board with the Cooler Master case. Despite the premium price, the active community support and the properly documented promise of a complete, working system made it easy to place an order with confidence.<br />
The SpacemiT K3 is poised to be one of the most significant RISC-V chips for local AI workloads, thanks to its RVA23 compliance and high tokens per second potential. Yet the buying experience in mid-2026 remains fragmented and incomplete. Hopefully this is just because the product is new, and they will get the purchase experience polished soon.<br />
What struck me most during this process was how poor the customer experience is across nearly all of these vendor websites: broken links, missing search functions, outdated product banners, pages that show the wrong product entirely, and no information about shipping times, stock levels, taxes, and so on. One wonders why these companies don’t fully invest in their web presence.<br />
Personally I would assume they likely have enough customers already, primarily through domestic channels like Taobao and JD.com, that they do not feel any pressure to improve their international-facing sites. However, I did also review what was offered on Taobao, and the product details were very incomplete there too. Taobao, however, has a built-in live chat with almost all sellers, which can be used to ask questions and thus compensate for missing product details.<br />
I don’t fully understand why the sales process seems unpolished. The websites feel almost like an afterthought – a checkbox to claim global reach while the real business apparently happens elsewhere via closed platforms or via inaccessible reseller channels. It is a frustrating reminder that in the RISC-V hardware world, the technology may be open and global, but the purchase experience is less so.</p>
<p><a href="https://optimizedbyotto.com/post/buying-spacemit-k3-risc-v-ai-cpu/">SpacemiT K3 is a compelling RISC-V AI CPU, but difficult to buy</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p><img decoding="async" src="https://optimizedbyotto.com/post/buying-spacemit-k3-risc-v-ai-cpu/spacemit-k3.jpg" alt="Featured image of post SpacemiT K3 is a compelling RISC-V AI CPU, but difficult to buy"></p>
<p>The RISC-V CPU architecture has been gaining a lot of popularity since it launched in 2014, and now that the industry is standardizing on the RVA23 level that includes vector support as a mandatory extension, we are likely to see a lot more edge- and IoT devices with the ability to run local LLMs at reasonable speed, and most importantly at very compelling prices.</p>
<p><a class="link" href="https://www.spacemit.com/" target="_blank" rel="noopener">SpacemiT</a> is a Chinese RISC-V CPU manufacturer that launched on May 11th, 2026, their <a class="link" href="https://canonical.com/blog/spacemit-announces-availability-of-ubuntu-on-k3-k1-series" target="_blank" rel="noopener">long-anticipated next-gen RISC-V</a> AI chip <a class="link" href="https://www.spacemit.com/products/keystone/k3" target="_blank" rel="noopener">K3</a>. It is among the earliest RISC-V CPUs that adhere to the <a class="link" href="https://www.heise.de/en/news/RISC-V-and-Linux-Ubuntu-25-10-forces-brand-new-processors-10538066.html" target="_blank" rel="noopener">RVA23 standard</a> and performance-wise it is quite capable, providing 130 KDMIPS general computing power, 60 TOPS on INT4 which translates to about 15 tokens per second when running a 30 billion parameter large language model.</p>
<p>The aspect that really makes it stand out is:</p>
<ul>
<li>the <a class="link" href="https://en.wikipedia.org/wiki/RISC-V" target="_blank" rel="noopener">RISC-V CPU architecture is open source</a>,</li>
<li>the price point is within reach of home and small business users and</li>
<li>the overall feature set makes it an ideal platform to build <strong><em>local</em> and <em>offline</em> AI systems</strong>.</li>
</ul>
<p>SpacemiT also develops their own Debian-based Linux distribution Bianbu OS, and seems to have collaboration going on with the wider community. Their <a class="link" href="https://www.spacemit.com/community" target="_blank" rel="noopener">community site</a> seems active, and they also have a dedicated <a class="link" href="https://x.com/spacemit_riscv" target="_blank" rel="noopener">X account @spacemit_riscv</a> and <a class="link" href="https://www.reddit.com/r/spacemit_riscv/comments/1t5yimh/upstream-progress-updates/" target="_blank" rel="noopener">Reddit account r/spacemit_riscv</a> posting relevant progress info on Linux kernel upstreaming activities. The X account is also responsive, as evidenced by <a class="link" href="https://x.com/ottokekalainen/status/2056375593722356207" target="_blank" rel="noopener">its replies to my questions</a>.</p>
<p>Canonical lists the SpacemiT K3 pico-ITX and K3 CoM260 Kit on its official <a class="link" href="https://ubuntu.com/download/risc-v/partner-built" target="_blank" rel="noopener">Ubuntu for RISC-V partner-built hardware page</a>, which strengthens the perception that upstream Linux support is being taken seriously. The SpacemiT folks also gave an interesting <a class="link" href="https://www.youtube.com/watch?v=BaY2l17OBRQ" target="_blank" rel="noopener">talk at the 2026 Ubuntu Summit</a> that includes a peek into their roadmap with future K3, K7 and K9 models.</p>
<p>For technical details, see SpacemiT&rsquo;s <a class="link" href="https://www.spacemit.com/community/development-kit/k3-pico-itx" target="_blank" rel="noopener">K3 pico-ITX documentation</a>, the Jetson Orin Nano-compatible <a class="link" href="https://www.spacemit.com/community/development-kit/k3-com260" target="_blank" rel="noopener">K3 CoM260 board documentation</a> and <a class="link" href="https://www.spacemit.com/community/document/info?lang=en&amp;nodepath=hardware/key_stone/k3" target="_blank" rel="noopener">documentation of the K3 processor itself</a>.</p>
<p><img decoding="async" src="https://optimizedbyotto.com/post/buying-spacemit-k3-risc-v-ai-cpu/spacemit-k3-pico-itx-and-k3-com260-kit.webp" width="1200" height="628" loading="lazy" alt="The SpacemiT K3 pico-ITX board and the K3 CoM260 board side-by-side (not to scale)" class="gallery-image" data-flex-grow="191" data-flex-basis="458px">
</p>
<h2 id="comparing-the-resellers"><a href="#comparing-the-resellers" class="header-anchor"></a>Comparing the resellers<br>
<a class="anchor-link" id="comparing-the-resellers"></a></h2>
<p>SpacemiT does not sell anything directly to consumers. Instead you need to buy a board that includes the K3 chip from an integrator. Currently the main resellers are:</p>
<ul>
<li><a class="link" href="#milkv">Milk-V</a></li>
<li><a class="link" href="#sipeed">Sipeed</a></li>
<li><a class="link" href="#banana-pi">Banana Pi</a></li>
<li><a class="link" href="#firefly">Firefly</a></li>
<li><a class="link" href="#deepcomputing">DeepComputing</a></li>
</ul>
<p>All of the above are Chinese companies that ship to customers both inside and outside China. DeepComputing stands out as the only one that actually has done real integration and ships the K3 on a custom board, while the others simply resell the SpacemiT-produced K3 pico-ITX and K3 CoM260 Kit.</p>
<h2 id="milk-v"><a href="#milk-v" class="header-anchor"></a>Milk-V<br>
<a class="anchor-link" id="milk-v"></a></h2>
<p>Milk-V is a RISC-V specialized integrator, as the name already implies. They sell the K3 under the name <a class="link" href="https://milkv.io/jupiter2" target="_blank" rel="noopener">Jupiter2</a>. Of all the K3 pico-ITX reseller product pages, the Jupiter2 presentation is the nicest and most detailed. Unfortunately their <a class="link" href="https://arace.tech/products/milk-v-jupiter-2" target="_blank" rel="noopener">order page at arace.tech</a> only states that it is a &ldquo;pre-order&rdquo; with no information about shipping schedule, taxes, or other details like what SSD is included (if any). Based on the pictures it does ship with a Milk-V branded case. The 32 GB RAM lists at 504 EUR, which is a very reasonable price. The <a class="link" href="https://x.com/MilkV_Official" target="_blank" rel="noopener">@MilkV_Official account on X</a> recently promoted the K3.</p>
<h3 id="documentation-and-support"><a href="#documentation-and-support" class="header-anchor"></a>Documentation and support<br>
<a class="anchor-link" id="documentation-and-support"></a></h3>
<p>As of this writing, the <a class="link" href="https://milkv.io/docs/jupiter2/" target="_blank" rel="noopener">Milk-V Jupiter2 documentation site</a> is just a stub and has no actual content, and only two links to the SpacemiT K3 documentation site. For support there is a web forum with a <a class="link" href="https://community.milkv.io/c/jupiter/jupiter2/19" target="_blank" rel="noopener">dedicated Jupiter2 section</a>. There is also a <a class="link" href="https://matrix.to/#/#milk-v:matrix.org" target="_blank" rel="noopener">Matrix space</a>, but unlike their other products, there is no dedicated Jupiter (neither v1 nor v2) channel.</p>
<h3 id="community-size-and-open-source-involvement"><a href="#community-size-and-open-source-involvement" class="header-anchor"></a>Community size and open source involvement<br>
<a class="anchor-link" id="community-size-and-open-source-involvement"></a></h3>
<p>At least one prior Milk-V product <a class="link" href="https://canonical.com/blog/canonical-enables-ubuntu-on-milk-v-mars" target="_blank" rel="noopener">was certified by Canonical</a>, which indicates there is some collaboration in progress. Canonical also lists the <a class="link" href="https://ubuntu.com/download/risc-v/partner-built" target="_blank" rel="noopener">Milk-V Titan</a> on its official Ubuntu for RISC-V partner-built hardware page.</p>
<h2 id="sipeed"><a href="#sipeed" class="header-anchor"></a>Sipeed<br>
<a class="anchor-link" id="sipeed"></a></h2>
<p>The <a class="link" href="https://sipeed.com/k3" target="_blank" rel="noopener">Sipeed K3 announcement</a> is well written (in English) with all the relevant details and links to additional PDF manuals. However, their main page at <a class="link" href="https://sipeed.com/" target="_blank" rel="noopener">sipeed.com</a> says nothing about the K3, so one must know the subpage URL to access it. They offer both the K3 CoM260 kit compatible with Jetson Orin Nano carrier boards, and the stand-alone K3 pico-ITX-sized motherboard. The CoM260 kit is only 10 USD cheaper than the full pico-ITX motherboard, so choosing the latter is a no-brainer if starting from scratch. The pico-ITX model with 32 GB DDR5 RAM sells for 639 USD. The product page does not mention anything about hard disk size, so you don&rsquo;t really know exactly what you will be getting if placing an order. There is no indication about case, Wi-Fi antennas or power supply either, so most likely they are not included.</p>
<p>Their <a class="link" href="http://store.sipeed.com" target="_blank" rel="noopener">store.sipeed.com</a> website does not work at all, and their Taobao and AliExpress stores are not public and only accessible to registered users. The order page also says nothing about shipping time, delivery time, or taxes. The <a class="link" href="https://x.com/SipeedIO/status/2055549071931404291" target="_blank" rel="noopener">X account @SipeedIO</a> is active and recently posted pictures of shipments in progress.</p>
<h3 id="documentation-and-support-1"><a href="#documentation-and-support-1" class="header-anchor"></a>Documentation and support<br>
<a class="anchor-link" id="documentation-and-support"></a></h3>
<p>The main <a class="link" href="https://wiki.sipeed.com/" target="_blank" rel="noopener">documentation wiki</a> does not yet have any K3 content at the time of writing. There is a <a class="link" href="https://discord.com/channels/1359800784375644291/1503600021646479500" target="_blank" rel="noopener">Discord channel for general RISC-V discussion</a>, and their MaixHub also has a discussion board, but I didn&rsquo;t find anything K3-specific.</p>
<h3 id="community-size-and-open-source-involvement-1"><a href="#community-size-and-open-source-involvement-1" class="header-anchor"></a>Community size and open source involvement<br>
<a class="anchor-link" id="community-size-and-open-source-involvement"></a></h3>
<p>Sipeed has had at least one of their previous devices <a class="link" href="https://ubuntu.com/blog/canonical-enables-ubuntu-on-sipeeds-licheerv-risc-v-board" target="_blank" rel="noopener">certified by Canonical</a>, which indicates they are active in the community.</p>
<p>Note that the other RISC-V company <a class="link" href="https://ubuntu.com/tutorials/how-to-install-ubuntu-on-risc-v-hifive-boards" target="_blank" rel="noopener">SiFive</a> that <a class="link" href="https://canonical.com/blog/sifive-eswin-computing-and-canonical-announce-availability-of-ubuntu-on-the-hifive-premier-p550" target="_blank" rel="noopener">also</a> has had hardware certified and officially supported by Canonical is a different company, despite the very similar name.</p>
<h2 id="banana-pi"><a href="#banana-pi" class="header-anchor"></a>Banana Pi<br>
<a class="anchor-link" id="banana-pi"></a></h2>
<p><a class="link" href="https://banana-pi.org/en/product-news/591.html" target="_blank" rel="noopener">Banana Pi announced</a> that they offer both the K3 CoM260 kit and the K3 pico-ITX motherboard version. Their <a class="link" href="https://banana-pi.org/en/core-board-and-kit/207.html" target="_blank" rel="noopener">product page for the K3</a> confusingly shows a MediaTek product in the page banner rather than the SpacemiT K3. Based on the product description and the fact they renamed the product as <em>BPI-SM10</em>, it seems to ship with some carrier board. The product pictures look identical to the SpacemiT documentation and there is no picture of the carrier board, and details are very sparse. The <a class="link" href="https://www.bpi-shop.com/products/k3-pico-itx-spacemit-k3-8-cores--60tops-al-performance-wifi6.html" target="_blank" rel="noopener">pico-ITX version</a> with 8 GB RAM and 128 GB SSD sells for 293 USD and the <a class="link" href="https://www.bpi-shop.com/products/bpi-sm10-k3-com260.html" target="_blank" rel="noopener">CoM260 developer kit</a> with the same specs sells for 287 USD and the 32 GB RAM with 128 GB SSD model sells for 595 USD. The shop page shows only five orders so far and items are currently out of stock. As there was no 32 GB RAM version of the pico-ITX available at all, this isn&rsquo;t an option for me as I want to run 30B parameter models that need the larger memory version.</p>
<p>Of all of these resellers, the <strong>Banana Pi website seems the most outdated</strong>. It does not have a search feature, it is not mobile-friendly, pictures can&rsquo;t be pinched to zoom in and so forth. Product names are also almost all identical, and as the product listings only show the beginning of the product name, figuring out what product is what requires extra effort that just makes the online purchase experience plain bad.</p>
<h3 id="documentation-and-support-2"><a href="#documentation-and-support-2" class="header-anchor"></a>Documentation and support<br>
<a class="anchor-link" id="documentation-and-support"></a></h3>
<p>I was only able to find the <a class="link" href="https://docs.banana-pi.org/en/BPI-SM10/BananaPi_BPI-SM10" target="_blank" rel="noopener">documentation page for the CoM260 kit</a>, but none for the pico-ITX version. For support there is a <a class="link" href="https://forum.banana-pi.org/" target="_blank" rel="noopener">forum</a>, but the category list does not show any section for K3, and the forum search prohibits using the search term &ldquo;k3&rdquo; as too short.</p>
<h3 id="community-size-and-open-source-involvement-2"><a href="#community-size-and-open-source-involvement-2" class="header-anchor"></a>Community size and open source involvement<br>
<a class="anchor-link" id="community-size-and-open-source-involvement"></a></h3>
<p>Banana Pi has a long history in the ARM single-board computer market, but their presence in the RISC-V ecosystem is still growing. Their <a class="link" href="https://x.com/sinovoip" target="_blank" rel="noopener">X account @sinovoip</a> has posted only once about the K3 and otherwise promotes their ARM boards. However, their <a class="link" href="https://banana-pi.org/en/community-culture/" target="_blank" rel="noopener">community culture page</a> does express a commitment to open hardware in general, but there is no visible K3-specific community activity.</p>
<h2 id="firefly"><a href="#firefly" class="header-anchor"></a>Firefly<br>
<a class="anchor-link" id="firefly"></a></h2>
<p><a class="link" href="https://en.t-firefly.com/p/aibox-k3" target="_blank" rel="noopener">Firefly&rsquo;s K3 product page</a> is comprehensive. Based on the details, they do not offer the K3 pico-ITX variant at all, but only the K3 CoM260 board inside the AIBOX-K3 Firefly RISC-V Edge Mini PC product. This is a feature-complete offering with a Jetson Orin Nano carrier board and case. The AIBOX-K3 with 32 GB RAM and 128 GB SSD in a case sells for 689 USD in their own <a class="link" href="https://www.firefly.store/products/aibox-k3-risc-v-edge-mini-pc?variant=46857894821972" target="_blank" rel="noopener">Firefly.store</a>. Unfortunately it only has HDMI and there is no USB-C with DisplayPort support, which is a deal-breaker for me personally.</p>
<p>Interestingly, Firefly also offers <a class="link" href="https://www.firefly.store/blogs/news/firefly-k3-series-launches-with-powerful-risc-v-chips-supports-30b-ai-models" target="_blank" rel="noopener">rack-mounted servers with K3</a> as the CPU.</p>
<h3 id="documentation-and-support-3"><a href="#documentation-and-support-3" class="header-anchor"></a>Documentation and support<br>
<a class="anchor-link" id="documentation-and-support"></a></h3>
<p>The wiki link on the product page is broken. The <a class="link" href="https://en.t-firefly.com/wiki" target="_blank" rel="noopener">Firefly wiki</a> does have a section for the AIBOX-K3, but it too has a broken link. It seems that as of the time of writing, there is no wiki section for this product yet.</p>
<p>For support there is a <a class="link" href="https://bbs.t-firefly.com/" target="_blank" rel="noopener">web forum</a>, which does have at least <a class="link" href="https://bbs.t-firefly.com/forum.php?mod=viewthread&amp;tid=66517&amp;extra=page%3D1" target="_blank" rel="noopener">one K3 thread</a> covering guides such as Hermes Agent installation, though broader K3-specific sections are still sparse.</p>
<h3 id="community-size-and-open-source-involvement-3"><a href="#community-size-and-open-source-involvement-3" class="header-anchor"></a>Community size and open source involvement<br>
<a class="anchor-link" id="community-size-and-open-source-involvement"></a></h3>
<p>Firefly&rsquo;s <a class="link" href="https://x.com/TeeFirefly" target="_blank" rel="noopener">X account @TeeFirefly</a> has had no posts since 2024, and their <a class="link" href="https://gitlab.com/T-Firefly" target="_blank" rel="noopener">GitLab/T-Firefly</a> shows mostly 2024 activity, with only one repository updated in 2025 and nothing in 2026. Historically they have built a moderate community around their ARM-based Rockchip boards, with active forums and wiki contributions for those product lines. Their RISC-V K3 offerings are newer, and likely need a lot more polish to be attractive products overall.</p>
<h2 id="deepcomputing"><a href="#deepcomputing" class="header-anchor"></a>DeepComputing<br>
<a class="anchor-link" id="deepcomputing"></a></h2>
<p>Last, but certainly not least, is the laptop manufacturer <a class="link" href="https://deepcomputing.io" target="_blank" rel="noopener">DeepComputing</a> that offers a <a class="link" href="https://deepcomputing.io/product/dc-roma-risc-v-mainboard-iii/" target="_blank" rel="noopener">Framework laptop compatible motherboard with the SpacemiT K3 chip</a>. They also sell the plain motherboard, or with the Cooler Master case, which allows one to easily connect it to an external monitor and keyboard and use it as a desktop computer. The plain board with 32 GB RAM and no SSD sells for about 882 EUR. Shipping of the first batch is expected to start by end of June 2026. Their <a class="link" href="https://x.com/DeepComputingio" target="_blank" rel="noopener">X account @DeepComputingio</a> promotes this DC-ROMA RISC-V Mainboard III as their flagship product, so they seem to put a lot of effort into it.</p>
<p>The overall product design and packaging seems good. Of all the K3 resellers and integrators that I was able to find, <strong>DeepComputing is the only one that actually designs their own boards</strong> with the K3 processor, while all the other vendors above are simply reselling the vanilla K3 boards with or without a case.</p>
<p><strong>After reviewing all these options I decided to buy the <a class="link" href="https://store.deepcomputing.io/products/dc-roma-risc-v-mainboard-iii-for-framework-laptop-13?variant=51310183088292" target="_blank" rel="noopener">DC-ROMA RISC-V Mainboard III</a></strong> for Framework Laptop 13 with 32 GB RAM, 1 TB SSD and the Cooler Master case, totalling about 1100 EUR.</p>
<h3 id="documentation-and-support-4"><a href="#documentation-and-support-4" class="header-anchor"></a>Documentation and support<br>
<a class="anchor-link" id="documentation-and-support"></a></h3>
<p>DeepComputing maintains product information for their RISC-V hardware at <a class="link" href="https://github.com/DC-DeepComputing/Framework" target="_blank" rel="noopener">github.com/DC-DeepComputing/Framework</a>, with documentation of the newest <em>Mainboard III (FML13V05)</em> still being finalized ahead of the first batch shipment. They provide community support through <a class="link" href="https://discord.com/invite/DycykxSxWH" target="_blank" rel="noopener">Discord</a> and <a class="link" href="https://deepcomputing.discourse.group/" target="_blank" rel="noopener">web forum</a>, although the latter has very little activity.</p>
<h3 id="community-size-and-open-source-involvement-4"><a href="#community-size-and-open-source-involvement-4" class="header-anchor"></a>Community size and open source involvement<br>
<a class="anchor-link" id="community-size-and-open-source-involvement"></a></h3>
<p>DeepComputing has established itself as a pioneer in RISC-V laptops, beginning with the DC-ROMA. I have seen their stand at FOSDEM, which shows they are genuinely active in the open source community. Canonical lists <a class="link" href="https://ubuntu.com/download/risc-v/partner-built" target="_blank" rel="noopener">DeepComputing&rsquo;s first mainboard / FML13V01</a> on its official Ubuntu for RISC-V partner-built hardware page, and it seems likely that they will continue to collaborate with Canonical with the new model once it ships. While the underlying Linux enablement depends on SpacemiT&rsquo;s upstream efforts, DeepComputing&rsquo;s involvement helps bridge the gap between reference hardware and consumer-ready products.</p>
<p><img decoding="async" src="https://optimizedbyotto.com/post/buying-spacemit-k3-risc-v-ai-cpu/deepcomputing-cool-master-spacemit-k3.webp" width="531" height="381" loading="lazy" alt="DeepComputing K3 board in the Cooler Master case" class="gallery-image" data-flex-grow="139" data-flex-basis="334px">
</p>
<h2 id="conclusion"><a href="#conclusion" class="header-anchor"></a>Conclusion<br>
<a class="anchor-link" id="conclusion"></a></h2>
<p>After weighing all the options, I ended up placing an order with DeepComputing for their custom K3 board with the Cooler Master case. Despite the premium price, the active community support and the properly documented promise of a complete, working system made it easy to place an order with confidence.</p>
<p>The SpacemiT K3 is poised to be one of the most significant RISC-V chips for local AI workloads, thanks to its RVA23 compliance and high tokens per second potential. Yet the buying experience in mid-2026 remains fragmented and incomplete. Hopefully this is just because the product is new, and they will get the purchase experience polished soon.</p>
<p>What struck me most during this process was how poor the customer experience is across nearly all of these vendor websites: broken links, missing search functions, outdated product banners, pages that show the wrong product entirely, and no information about shipping times, stock levels, taxes, and so on. One wonders why these companies don&rsquo;t fully invest in their web presence.</p>
<p>Personally I would assume they <strong>likely have enough customers already,</strong> primarily through domestic channels like <em>Taobao</em> and <em>JD.com</em>, that they do not feel any pressure to improve their international-facing sites. However, I did also review what was offered on Taobao, and the product details were very incomplete there too. Taobao, however, has a built-in live chat with almost all sellers, which can be used to ask questions and thus compensate for missing product details.</p>
<p>I don&rsquo;t fully understand why the sales process seems unpolished. The websites feel almost like an afterthought &ndash; a checkbox to claim global reach while the real business apparently happens elsewhere via closed platforms or via inaccessible reseller channels. It is a frustrating reminder that in the RISC-V hardware world, the technology may be open and global, but the purchase experience is less so.</p>

<p><a href="https://optimizedbyotto.com/post/buying-spacemit-k3-risc-v-ai-cpu/">SpacemiT K3 is a compelling RISC-V AI CPU, but difficult to buy</a> appeared first on <a 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><a 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 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><a 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 href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>Inserting in Two Tables in a Single Round-Trip with JSON Duality Views in MySQL 9.7</title>
      <link rel="alternate" type="text/html" href="https://jfg-mysql.blogspot.com/2026/06/inserting-in-two-tables-in-single-round-trip.html" />
      <id>https://jfg-mysql.blogspot.com/2026/06/inserting-in-two-tables-in-single-round-trip.html</id>
      <updated>2026-06-04T14:17:32+00:00</updated>
      <author><name>Jean-François Gagné</name></author>
      <summary type="html"><![CDATA[<p>A few months ago, I was asking myself how to insert in two tables in a single round-trip to the database.&#160; I wanted to do that to optimize a process.&#160; My optimization involved splitting a table in two, which would need inserting in two tables atomically.&#160; The downside was changing an auto-commit INSERT to a transaction with two inserts, which was changing the shape of the workload</p>
<p><a href="https://jfg-mysql.blogspot.com/2026/06/inserting-in-two-tables-in-single-round-trip.html">Inserting in Two Tables in a Single Round-Trip with JSON Duality Views in MySQL 9.7</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>A few months ago, I was asking myself how to insert in two tables in a single round-trip to the database.&amp;nbsp; I wanted to do that to optimize a process.&amp;nbsp; My optimization involved splitting a table in two, which would need inserting in two tables atomically.&amp;nbsp; The downside was changing an auto-commit INSERT to a transaction with two inserts, which was changing the shape of the workload</p>

<p><a href="https://jfg-mysql.blogspot.com/2026/06/inserting-in-two-tables-in-single-round-trip.html">Inserting in Two Tables in a Single Round-Trip with JSON Duality Views in MySQL 9.7</a> appeared first on <a 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><a href="https://mariadb.org/the-power-of-the-community/">The Power Of The Community!</a> appeared first on <a 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><a href="https://mariadb.org/the-power-of-the-community/">The Power Of The Community!</a> appeared first on <a 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><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://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 loading="lazy" decoding="async" 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><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://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><a 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 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><a 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 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><a href="https://mariadb.org/mariadb-hidden-gem-create-aggregate-function/">MariaDB Hidden Gem: Create Aggregate Function</a> appeared first on <a 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><a href="https://mariadb.org/mariadb-hidden-gem-create-aggregate-function/">MariaDB Hidden Gem: Create Aggregate Function</a> appeared first on <a 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><a 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 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><a 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 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><a 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 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><a 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 href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
      <entry>
      <title>I got swarmed by a replication issue</title>
      <link rel="alternate" type="text/html" href="https://medium.com/@arbaudie.it/i-got-swarmed-by-a-replication-issue-556c179783cc?source=rss-c779d007e7fe------2" />
      <id>https://medium.com/@arbaudie.it/i-got-swarmed-by-a-replication-issue-556c179783cc?source=rss-c779d007e7fe------2</id>
      <updated>2026-06-02T20:18:02+00:00</updated>
      <author><name>ArBauDie.IT</name></author>
      <summary type="html"><![CDATA[<p>I recently worked with a client who runs a mature CI/CD pipeline on GitLab. Docker Swarm as the orchestrator, config files versioned and pulled at deploy time, the whole nine yards. They had been deploying standalone MariaDB instances this way for a while, without a hitch.Then came the ask : stand up an async replication cluster. One primary, two replicas, one MaxScale instance sitting in front of it all. Quite classical, nothing out of the ordinary really.First deployment ? Smooth. Replication is running, MaxScale routing reads to the replicas, writes to the primary. We are all happy.Then we had to redeploy the replicas.Every time we redeployed the replica containers, replication broke. Every time, the fix was the same manual ceremony : connect to each replica, run CHANGE MASTER TO, START SLAVE, check SHOW SLAVE STATUS. Everything works fine after that.Until the next redeployment that is.Same config files, same image. No changes, just as promised by the CI/CD pipeline. Or so i thought.The error log is always your first stop in this situation. And it told us exactly what was wrong :[ERROR] Failed to open the relay log \'./568165be8cc3-relay-bin.000002\' (relay_log_pos 4463864)[ERROR] Could not find target log during relay log initialization[ERROR] Failed to initialize the master info structureThe replica was looking for a relay log file that did not exist. But it took me a while (3 hours actually) to connect the dots as i focused on the last line for a while.Here is the thing about MariaDB relay log file naming : by default MariaDB derives the relay log basename from the server\'s hostname. On a bare metal or VM setup, that hostname is immutable. You set it once, it never changes.In Docker, not so much. By default, Docker does set a container’s hostname to its short container ID — a random hash that changes at every docker run or container recreation. Swarm makes it even worse as it also rotates task IDs on every redeployment, so even if we would try and rely on some predictable naming pattern, Swarm would break it further. A task that was replica_1.1.xk3f8a9b2c becomes replica_1.1.yz9q2m7nkp after redeployment.So on the previous deployment, the relay logs were named something like xk3f8a9b2c-relay-bin.000001. On the new deployment, the relay log index file references those old names. The new container starts up, checks the index, finds filenames that don\'t match its own generated basename (yz9q2m7nkp-relay-bin.000001) , can\'t locate the relay logs, and replication fails to resume.The root cause : i had not explicitly set the relay log basename in configuration files. I had set everything else. Just not that.Fixing it only took one line in my.cnf. Yes, one line and voilà !relay_log     = relay-binHardcoding the relay log basename decouples replication state from container identity. Whatever hostname Docker Swarm decides to assign to the replica container, the relay log filenames stay constant. Replication resumes cleanly.While you’re at it, a few other directives are worth reviewing in any containerized replication setup :log_slave_updates = 1 — useful if you ever plan to chain replicas or use the replicas as a source for another downstream replica. Good habit regardless.relay_log_purge = 1 — keeps relay logs cleaned up automatically. In a container environment with limited storage you really want this on.relay_log_recovery = 1 — instructs the replica to recover relay log state from the master position on startup rather than relying on the relay log index. A solid safety net in ephemeral environments.expire_logs_days = 5 — instructs the replica to delete any binary log file in which the last event is older than 5 days. Helps with disk capacity planning.MaxScale was configured to monitor replication state on the replicas. And it did exactly what it was supposed to : the moment replication stopped, it pulled both replicas out of the read pool and flagged them as unavailable.MaxScale hid this bug from the app as expected. It also helped notice the issue. Proper replication monitoring in your proxy layer is not optional.One missing line in a configuration file ended up with three hours of head-scratching because of the following (wrong) assumptions :Docker Swarm and stateful services do not share the same concept of identity. Containers are stateless disposable resources, cattle. Rename, kill, redeploy as you see fit. Databases on the other hand are very much stateful. Stability is an implicit contract, including the hostname.Config-as-code is not the same as config completeness. The GitLab-driven deployment pipeline was running smooth. The config was versioned, reviewed, deployed automatically. The pipeline does exactly what it is instructed to, it does not tell what one forgot.Any MariaDB system variable that derives its default from a system value is a redeployment time-bomb in orchestrated environments. relay_log is the one that got our focus here. But the same logic applies to many other variables. If you run MariaDB in a stateless environment set these explicitly. Always.Stateful services in stateless environments will always bite back if you let the environment make assumptions on your behalf. The fix is one line but the overall lesson is to never let the orchestrator decide what you should control.Lessons learned !!If you’ve ever spent an afternoon staring at SHOW SLAVE STATUS wondering why a cluster that worked yesterday doesn\'t work today after a \"routine\" redeployment — I hope this saves you some trouble.Do not hesitate to reach out if you want to discuss your replication architecture or containerized database setup.</p>
<p><a href="https://medium.com/@arbaudie.it/i-got-swarmed-by-a-replication-issue-556c179783cc?source=rss-c779d007e7fe------2">I got swarmed by a replication issue</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></summary>
      <content type="html"><![CDATA[<p>I recently worked with a client who runs a mature CI/CD pipeline on GitLab. Docker Swarm as the orchestrator, config files versioned and pulled at deploy time, the whole nine yards. They had been deploying standalone MariaDB instances this way for a while, without a&nbsp;hitch.</p>
<p>Then came the ask&nbsp;: stand up an async replication cluster. One primary, two replicas, one MaxScale instance sitting in front of it all. Quite classical, nothing out of the ordinary&nbsp;really.</p>
<p>First deployment&nbsp;? Smooth. Replication is running, MaxScale routing reads to the replicas, writes to the primary. We are all&nbsp;happy.</p>
<p>Then we had to redeploy the replicas.</p>
<p>Every time we redeployed the replica containers, replication broke. <br>Every time, the fix was the same manual ceremony&nbsp;: connect to each replica, run CHANGE MASTER TO, START SLAVE, check SHOW SLAVE STATUS. Everything works fine after&nbsp;that.</p>
<p>Until the next redeployment that&nbsp;is.</p>
<p>Same config files, same image. No changes, just as promised by the CI/CD pipeline. Or so i&nbsp;thought.</p>
<p>The error log is always your first stop in this situation. And it told us exactly what was wrong&nbsp;:</p>
<pre>[ERROR] Failed to open the relay log './568165be8cc3-relay-bin.000002' (relay_log_pos 4463864)<br>[ERROR] Could not find target log during relay log initialization<br>[ERROR] Failed to initialize the master info structure</pre>
<p>The replica was looking for a relay log file that did not exist. But it took me a while (3 hours actually) to connect the dots as i focused on the last line for a&nbsp;while.</p>
<p>Here is the thing about MariaDB relay log file naming&nbsp;: by default MariaDB derives the relay log basename from the server's hostname. On a bare metal or VM setup, that hostname is immutable. You set it once, it never&nbsp;changes.</p>
<p>In Docker, not so much. By default, Docker does set a container&rsquo;s hostname to its short container ID&#8202;&mdash;&#8202;a random hash that changes at every docker run or container recreation. Swarm makes it even worse as it also rotates task IDs on every redeployment, so even if we would try and rely on some predictable naming pattern, Swarm would break it further. A task that was replica_1.1.xk3f8a9b2c becomes replica_1.1.yz9q2m7nkp after redeployment.</p>
<p>So on the previous deployment, the relay logs were named something like xk3f8a9b2c-relay-bin.000001. On the new deployment, the relay log index file references those old names. The new container starts up, checks the index, finds filenames that don't match its own generated basename (yz9q2m7nkp-relay-bin.000001)&nbsp;, can't locate the relay logs, and replication fails to&nbsp;resume.</p>
<p>The root cause&nbsp;: i had not explicitly set the relay log basename in configuration files. I had set everything else. Just not&nbsp;that.</p>
<p>Fixing it only took one line in my.cnf. Yes, one line and voil&agrave;&nbsp;!</p>
<pre>relay_log          = relay-bin</pre>
<p>Hardcoding the relay log basename decouples replication state from container identity. Whatever hostname Docker Swarm decides to assign to the replica container, the relay log filenames stay constant. Replication resumes&nbsp;cleanly.</p>
<p>While you&rsquo;re at it, a few other directives are worth reviewing in any containerized replication setup&nbsp;:</p>
<ul>
<li>log_slave_updates = 1&#8202;&mdash;&#8202;useful if you ever plan to chain replicas or use the replicas as a source for another downstream replica. Good habit regardless.</li>
<li>relay_log_purge = 1&#8202;&mdash;&#8202;keeps relay logs cleaned up automatically. In a container environment with limited storage you really want this&nbsp;on.</li>
<li>relay_log_recovery = 1&#8202;&mdash;&#8202;instructs the replica to recover relay log state from the master position on startup rather than relying on the relay log index. A solid safety net in ephemeral environments.</li>
<li>expire_logs_days = 5&#8202;&mdash;&#8202;instructs the replica to delete any binary log file in which the last event is older than 5 days. Helps with disk capacity planning.</li>
</ul>
<p>MaxScale was configured to monitor replication state on the replicas. And it did exactly what it was supposed to&nbsp;: the moment replication stopped, it pulled both replicas out of the read pool and flagged them as unavailable.</p>
<p>MaxScale hid this bug from the app as expected. It also helped notice the issue. Proper replication monitoring in your proxy layer is not optional.</p>
<p>One missing line in a configuration file ended up with three hours of head-scratching because of the following (wrong) assumptions&nbsp;:</p>
<ol>
<li><strong>Docker Swarm and stateful services do not share the same concept of identity.</strong> Containers are stateless disposable resources, cattle. Rename, kill, redeploy as you see fit. Databases on the other hand are very much stateful. Stability is an implicit contract, including the hostname.</li>
<li><strong>Config-as-code is not the same as config completeness.</strong> The GitLab-driven deployment pipeline was running smooth. The config was versioned, reviewed, deployed automatically. The pipeline does exactly what it is instructed to, it does not tell what one&nbsp;forgot.</li>
<li><strong>Any MariaDB system variable that derives its default from a system value is a redeployment time-bomb in orchestrated environments.</strong> relay_log is the one that got our focus here. But the same logic applies to many other variables. If you run MariaDB in a stateless environment set these explicitly. Always.</li>
</ol>
<p>Stateful services in stateless environments will always bite back if you let the environment make assumptions on your behalf. The fix is one line but the overall lesson is to never let the orchestrator decide what you should&nbsp;control.</p>
<p>Lessons learned&nbsp;!!</p>
<p>If you&rsquo;ve ever spent an afternoon staring at SHOW SLAVE STATUS wondering why a cluster that worked yesterday doesn't work today after a "routine" redeployment&#8202;&mdash;&#8202;I hope this saves you some&nbsp;trouble.</p>
<p>Do not hesitate to <a href="https://arbaudie.it">reach out</a> if you want to discuss your replication architecture or containerized database&nbsp;setup.</p>
<p><img loading="lazy" decoding="async" src="https://medium.com/_/stat?event=post.clientViewed&amp;referrerSource=full_rss&amp;postId=556c179783cc" width="1" height="1" alt=""></p>

<p><a href="https://medium.com/@arbaudie.it/i-got-swarmed-by-a-replication-issue-556c179783cc?source=rss-c779d007e7fe------2">I got swarmed by a replication issue</a> appeared first on <a href="https://mariadb.org">MariaDB.org</a></p>
]]></content>
    </entry>
    </feed>