<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:content="http://purl.org/rss/1.0/modules/content/">

<channel>
  <title>Planet MySQL</title>
  <link>https://planet.mysql.com</link>
  <pubDate>Tue, 22 Sep 2026 12:59:27 +0000</pubDate>
  <language>en</language>
  <description>Planet MySQL - https://planet.mysql.com</description>

  <item>
    <title>Village News: MySQL News + Events (21 September 2026)</title>
    <guid isPermaLink="false">6aa855285b6b4000010bbb6e</guid>
    <link>https://villagesql.com/blog/village-news-mysql-news-events-21-september-2026/</link>
    <description>Welcome back. This issue covers five weeks rather than the usual one — the gap since the August issue took in Percona Live Amsterdam, the launch of the OurSQL Foundation, and the run-up to the MySQL Galera Cluster end of life on 30 September.If you want to get these updates, just subscribe to the blog.Enjoy!MySQL NewsNote: Aggregated MySQL news can be found at Planet for MySQL Community and Planet MySQL (Oracle curated)DISTANCE() and VECTOR_DISTANCE(): Vector Similarity in Percona Server for MySQL 9.7 MySQL Performance Blog, PerconaTL;DR - Percona Server for MySQL 9.7.2-2 adds a DISTANCE() function with COSINE, EUCLIDEAN, MANHATTAN and DOT metrics, so embeddings can be ranked in SQL. ANN indexing is still to come.Using DuckDB inside MySQL VillageSQLTL;DR - A new VillageSQL Server extension runs DuckDB queries from inside MySQL and joins the results with MySQL result sets, aimed at querying Parquet and other analytical formats in place.Group Replication Beyond a Single Cluster: DC-DR with Percona (PS MySQL) Operator MySQL Performance Blog, PerconaTL;DR - Percona's PS MySQL Operator v1.2.0 adds cross-site replication for Group Replication/InnoDB Cluster topologies. The post walks through wiring a DR cluster to a primary one.Traceability Matters: Gopal Shankar on Opening Up MySQL Development for the Next Decade Roberto V. Zicari, ODBMS.orgTL;DR - Oracle's Gopal Shankar explains the reasoning behind moving Enterprise-tier features into Community Edition and what &quot;opening up&quot; MySQL development is meant to mean in practice.Vector search in MySQL: an early look at HNSW and custom indexes in VillageSQL VillageSQLTL;DR - MySQL 9.x ships a VECTOR type but no distance function and no vector index. This post covers an HNSW index built through VillageSQL's custom index support.Performance improvements in Percona Server 8.4.11-11 MySQL Performance Blog, PerconaTL;DR - A read/write benchmark breakdown of what changed in Percona Server for MySQL 8.4.11-11. It follows the earlier &quot;Performance Progression of Percona Server for MySQL 8.4&quot;, which is worth reading first.Diagnosing MySQL Memory Problems with Jemalloc Libing SongTL;DR - Performance Schema memory instrumentation is too coarse to find many leaks. This walks through using jemalloc's profiling to locate where MySQL memory actually goes.Percona releases Galera Cluster Version 9.7 Oli Sennhauser, FromDualTL;DR - Percona has shipped Galera Cluster 9.7 for MySQL, after MariaDB Corporation acquired Codership and announced the end of support for Galera Cluster for MySQL 8.4 this September.Introducing MySQL Workbench 26 Oracle MySQL GroupTL;DR - Workbench 26.7 is the first release of a rebuilt Workbench on a MySQL Shell foundation, replacing the end-of-life C++ Workbench 8. A companion post covers five things to try in it.Advanced Cryptography in MySQL with vsql-crypto VillageSQLTL;DR - The vsql-crypto extension brings common backend cryptography into MySQL itself: hashing stored passwords, signing payloads for downstream verification, and keeping a column unreadable outside the database.OAuth2 and JWT Logins for MySQL VillageSQLTL;DR - A VillageSQL extension that authenticates MySQL logins from OAuth2 and JWT tokens, so database access is revoked along with a departing engineer's single sign-on instead of being maintained separately in the database.OpenID Connect Authentication for MySQL, Now Fully Open Source MySQL Performance Blog, PerconaTL;DR - Percona Server for MySQL now ships an open source OIDC authentication plugin, letting accounts authenticate against any standards-compliant identity provider, from 8.4.11-11 and 9.7.2-2.Introducing the OurSQL Foundation OurSQL FoundationTL;DR - A re-launch of the OurSQL Foundation website. MySQL CPU and CSPU Versioning for More Frequent Security Updates The Oracle MySQL BlogTL;DR - Oracle is adding monthly Critical Security Patch Update releases alongside the quarterly CPU cycle, issued only when a critical security or stability fix warrants one.Announcing VillageSQL Server 0.0.6 VillageSQLTL;DR - VillageSQL Server 0.0.6 moves the mainline to MySQL Server 8.4.11 and adds support for MySQL Server 9.7.2 and Percona Server 8.4.10.MySQL Galera Cluster EOL: Your Practical Paths Forward ContinuentTL;DR - MySQL Galera Cluster reaches end of life on 30 September 2026. Five migration paths compared — MariaDB Galera, PXC, self-managed MySQL, managed cloud MySQL and Tungsten Cluster. A companion post covers the cost of delaying the decision.Database NewsThe architecture of Neki PlanetScaleTL;DR - PlanetScale's sharded Postgres, from the team behind Vitess, is in platform preview. Companion posts cover the router, the lifecycle of a sharded query, and a 118.5M queries/sec benchmark across 512 shards.Evaluating LLM models for DBA tasks Percona Database BlogTL;DR - A purpose-built harness measuring how well current LLMs handle real database administration tasks, rather than general systems admin.Introducing TIN: full-text search for Postgres Patrick Reynolds, PlanetScaleTL;DR - TIN is a new full-text search index for Postgres. A follow-up introduces Lead, a feature-equivalent version you can run in CI.Announcing ProxySQL 3.0.11, 3.1.11, and 4.0.11 ProxySQLTL;DR - Three parallel releases improving multiplexing, causal reads, modern authentication, fast-forward/TSDB reliability and MCP operations. ProxySQL.Cloud, a SaaS control plane for MySQL and Postgres, was also announced this month.Upcoming Database EventsMySQL BR Conf 2026 (VillageSQL is a sponsor) September 26, 2026 São Paulo, BrazilPostgres Summit US (formerly PGConf.NYC) September 30 – October 2, 2026 (PgUS) New York City, NYHigh Performance Transaction Systems (HPTS) October 4-7, 2026 Asilomar Conference Grounds Pacific Grove, CAOpen Source Summit Europe October 7-9, 2026 Prague, CzechiaAll Things Open 2026 October 19-20, 2026 Raleigh Convention Center Raleigh, NCPGConf.EU October 20–23, 2026 (PostgreSQL Europe) Valencia, SpainOracle AI World October 25–28, 2026 Las Vegas, NVKubeCon + CloudNativeCon North America (VillageSQL is a sponsor) November 9-12, 2026 Salt Lake City, UtahOpen Source Summit Japan December 7-9, 2026 Tokyo, JapanFOSDEM 2027 January 30-31, 2027 ULB Solbosch Campus Brussels, BelgiumKubeCon + CloudNativeCon Europe 2027 March 15-18, 2027 Barcelona, SpainSCaLE 24x April 1-4, 2027 Pasadena Convention Center Pasadena, CA</description>
    <content:encoded><![CDATA[<img src="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/images/2026/09/village-news-banner.jpg" alt="Village News: MySQL News + Events (21 September 2026)"><p>Welcome back. This issue covers five weeks rather than the usual one — the gap since the August issue took in Percona Live Amsterdam, the launch of the OurSQL Foundation, and the run-up to the MySQL Galera Cluster end of life on 30 September.</p><p>If you want to get these updates, just <a href="https://markdownlivepreview.com/blog#/portal/signup">subscribe</a> to the blog.</p><p>Enjoy!</p><h2>MySQL News</h2><p><em>Note: Aggregated MySQL news can be found at </em><a href="https://planet.oursqlcommunity.org/?source=villagesql"><em>Planet for MySQL Community</em></a><em> and </em><a href="https://planet.mysql.com/"><em>Planet MySQL</em></a><em> (Oracle curated)</em></p><p><a href="https://www.percona.com/blog/vector-distance-vector-similarity-search-in-percona-server-for-mysql/"><strong>DISTANCE() and VECTOR_DISTANCE(): Vector Similarity in Percona Server for MySQL 9.7</strong></a> <em>MySQL Performance Blog, Percona</em></p><p><strong>TL;DR -</strong> Percona Server for MySQL 9.7.2-2 adds a <code>DISTANCE()</code> function with COSINE, EUCLIDEAN, MANHATTAN and DOT metrics, so embeddings can be ranked in SQL. ANN indexing is still to come.</p><p><a href="https://villagesql.com/blog/duckdb/"><strong>Using DuckDB inside MySQL</strong></a> <em>VillageSQL</em></p><p><strong>TL;DR -</strong> A new VillageSQL Server extension runs DuckDB queries from inside MySQL and joins the results with MySQL result sets, aimed at querying Parquet and other analytical formats in place.</p><p><a href="https://www.percona.com/blog/group-replication-beyond-a-single-cluster-dc-dr-with-percona-ps-mysql-operator/"><strong>Group Replication Beyond a Single Cluster: DC-DR with Percona (PS MySQL) Operator</strong></a> <em>MySQL Performance Blog, Percona</em></p><p><strong>TL;DR -</strong> Percona's PS MySQL Operator v1.2.0 adds cross-site replication for Group Replication/InnoDB Cluster topologies. The post walks through wiring a DR cluster to a primary one.</p><p><a href="https://www.odbms.org/blog/2026/09/traceability-matters-gopal-shankar-on-opening-up-mysql-development-for-the-next-decade/"><strong>Traceability Matters: Gopal Shankar on Opening Up MySQL Development for the Next Decade</strong></a> <em>Roberto V. Zicari, ODBMS.org</em></p><p><strong>TL;DR -</strong> Oracle's Gopal Shankar explains the reasoning behind moving Enterprise-tier features into Community Edition and what "opening up" MySQL development is meant to mean in practice.</p><p><a href="https://villagesql.com/blog/vector-search-hnsw/"><strong>Vector search in MySQL: an early look at HNSW and custom indexes in VillageSQL</strong></a> <em>VillageSQL</em></p><p><strong>TL;DR -</strong> MySQL 9.x ships a VECTOR type but no distance function and no vector index. This post covers an HNSW index built through VillageSQL's custom index support.</p><p><a href="https://www.percona.com/blog/performance-improvements-in-percona-server-8-4-11-11/"><strong>Performance improvements in Percona Server 8.4.11-11</strong></a> <em>MySQL Performance Blog, Percona</em></p><p><strong>TL;DR -</strong> A read/write benchmark breakdown of what changed in Percona Server for MySQL 8.4.11-11. It follows the earlier "Performance Progression of Percona Server for MySQL 8.4", which is worth reading first.</p><p><a href="https://songlibing.github.io/posts/mysql-jemalloc-en/"><strong>Diagnosing MySQL Memory Problems with Jemalloc</strong></a> <em>Libing Song</em></p><p><strong>TL;DR -</strong> Performance Schema memory instrumentation is too coarse to find many leaks. This walks through using jemalloc's profiling to locate where MySQL memory actually goes.</p><p><a href="https://www.fromdual.com/blog/percona/percona-xtradb-galera-cluster-9-7-released/"><strong>Percona releases Galera Cluster Version 9.7</strong></a> <em>Oli Sennhauser, FromDual</em></p><p><strong>TL;DR -</strong> Percona has shipped Galera Cluster 9.7 for MySQL, after MariaDB Corporation acquired Codership and announced the end of support for Galera Cluster for MySQL 8.4 this September.</p><p><a href="https://blogs.oracle.com/mysql/introducing-mysql-workbench-26"><strong>Introducing MySQL Workbench 26</strong></a> <em>Oracle MySQL Group</em></p><p><strong>TL;DR -</strong> Workbench 26.7 is the first release of a rebuilt Workbench on a MySQL Shell foundation, replacing the end-of-life C++ Workbench 8. A companion post covers five things to try in it.</p><p><a href="https://villagesql.com/blog/crypto/"><strong>Advanced Cryptography in MySQL with vsql-crypto</strong></a> <em>VillageSQL</em></p><p><strong>TL;DR -</strong> The vsql-crypto extension brings common backend cryptography into MySQL itself: hashing stored passwords, signing payloads for downstream verification, and keeping a column unreadable outside the database.</p><p><a href="https://villagesql.com/blog/oauth2/"><strong>OAuth2 and JWT Logins for MySQL</strong></a> <em>VillageSQL</em></p><p><strong>TL;DR -</strong> A VillageSQL extension that authenticates MySQL logins from OAuth2 and JWT tokens, so database access is revoked along with a departing engineer's single sign-on instead of being maintained separately in the database.</p><p><a href="https://www.percona.com/blog/oidc-authentication-for-percona-mysql/"><strong>OpenID Connect Authentication for MySQL, Now Fully Open Source</strong></a> <em>MySQL Performance Blog, Percona</em></p><p><strong>TL;DR -</strong> Percona Server for MySQL now ships an open source OIDC authentication plugin, letting accounts authenticate against any standards-compliant identity provider, from 8.4.11-11 and 9.7.2-2.</p><p><a href="https://oursqlfoundation.org/blog/introducing-oursql-foundation/"><strong>Introducing the OurSQL Foundation</strong></a> <em>OurSQL Foundation</em></p><p><strong>TL;DR -</strong> A re-launch of the OurSQL Foundation website. </p><p><a href="https://blogs.oracle.com/mysql/mysql-cpu-and-cspu-versioning-for-more-frequent-security-updates"><strong>MySQL CPU and CSPU Versioning for More Frequent Security Updates</strong></a> <em>The Oracle MySQL Blog</em></p><p><strong>TL;DR -</strong> Oracle is adding monthly Critical Security Patch Update releases alongside the quarterly CPU cycle, issued only when a critical security or stability fix warrants one.</p><p><a href="https://villagesql.com/blog/006/"><strong>Announcing VillageSQL Server 0.0.6</strong></a> <em>VillageSQL</em></p><p><strong>TL;DR -</strong> VillageSQL Server 0.0.6 moves the mainline to MySQL Server 8.4.11 and adds support for MySQL Server 9.7.2 and Percona Server 8.4.10.</p><p><a href="https://www.continuent.com/resources/blog/mysql-galera-cluster-eol-your-practical-paths-forward"><strong>MySQL Galera Cluster EOL: Your Practical Paths Forward</strong></a> <em>Continuent</em></p><p><strong>TL;DR -</strong> MySQL Galera Cluster reaches end of life on 30 September 2026. Five migration paths compared — MariaDB Galera, PXC, self-managed MySQL, managed cloud MySQL and Tungsten Cluster. A companion post covers the cost of delaying the decision.</p><h2>Database News</h2><p><a href="https://planetscale.com/blog/the-architecture-of-neki"><strong>The architecture of Neki</strong></a> <em>PlanetScale</em></p><p><strong>TL;DR -</strong> PlanetScale's sharded Postgres, from the team behind Vitess, is in platform preview. Companion posts cover the router, the lifecycle of a sharded query, and a 118.5M queries/sec benchmark across 512 shards.</p><p><a href="https://www.percona.com/blog/evaluating-llm-models-for-dba-tasks/"><strong>Evaluating LLM models for DBA tasks</strong></a> <em>Percona Database Blog</em></p><p><strong>TL;DR -</strong> A purpose-built harness measuring how well current LLMs handle real database administration tasks, rather than general systems admin.</p><p><a href="https://planetscale.com/blog/introducing-tin"><strong>Introducing TIN: full-text search for Postgres</strong></a> <em>Patrick Reynolds, PlanetScale</em></p><p><strong>TL;DR -</strong> TIN is a new full-text search index for Postgres. A follow-up introduces Lead, a feature-equivalent version you can run in CI.</p><p><a href="https://proxysql.com/blog/announcing-proxysql-3-0-11/"><strong>Announcing ProxySQL 3.0.11, 3.1.11, and 4.0.11</strong></a> <em>ProxySQL</em></p><p><strong>TL;DR -</strong> Three parallel releases improving multiplexing, causal reads, modern authentication, fast-forward/TSDB reliability and MCP operations. ProxySQL.Cloud, a SaaS control plane for MySQL and Postgres, was also announced this month.</p><h2>Upcoming Database Events</h2><p><a href="https://comunidademysql.com.br/mysql-br-conf-2026/"><strong>MySQL BR Conf 2026</strong></a> (<strong>VillageSQL is a sponsor</strong>) September 26, 2026 São Paulo, Brazil</p><p><a href="https://2026.postgressummit.us/"><strong>Postgres Summit US (formerly PGConf.NYC)</strong></a> September 30 – October 2, 2026 (PgUS) New York City, NY</p><p><a href="https://hpts.ws/"><strong>High Performance Transaction Systems (HPTS)</strong></a> October 4-7, 2026 Asilomar Conference Grounds Pacific Grove, CA</p><p><a href="https://events.linuxfoundation.org/open-source-summit-europe/"><strong>Open Source Summit Europe</strong></a> October 7-9, 2026 Prague, Czechia</p><p><a href="https://2026.allthingsopen.org/"><strong>All Things Open 2026</strong></a> October 19-20, 2026 Raleigh Convention Center Raleigh, NC</p><p><a href="http://pgconf.eu/"><strong>PGConf.EU</strong></a> October 20–23, 2026 (PostgreSQL Europe) Valencia, Spain</p><p><a href="https://www.oracle.com/ai-world/"><strong>Oracle AI World</strong></a> October 25–28, 2026 Las Vegas, NV</p><p><a href="https://events.linuxfoundation.org/kubecon-cloudnativecon-north-america/"><strong>KubeCon + CloudNativeCon North America</strong></a> (<strong>VillageSQL is a sponsor</strong>) November 9-12, 2026 Salt Lake City, Utah</p><p><a href="https://events.linuxfoundation.org/open-source-summit-japan/"><strong>Open Source Summit Japan</strong></a> December 7-9, 2026 Tokyo, Japan</p><p><a href="https://fosdem.org/2027/"><strong>FOSDEM 2027</strong></a> January 30-31, 2027 ULB Solbosch Campus Brussels, Belgium</p><p><a href="https://events.linuxfoundation.org/kubecon-cloudnativecon-europe/"><strong>KubeCon + CloudNativeCon Europe 2027</strong></a> March 15-18, 2027 Barcelona, Spain</p><p><a href="https://www.socallinuxexpo.org/scale/24x"><strong>SCaLE 24x</strong></a> April 1-4, 2027 Pasadena Convention Center Pasadena, CA</p>]]></content:encoded>
    <pubDate>Mon, 21 Sep 2026 16:58:48 +0000</pubDate>
    <dc:creator>VillageSQL</dc:creator>
  </item>

  <item>
    <title>DISTANCE() and VECTOR_DISTANCE(): Vector Similarity in Percona Server for MySQL 9.7</title>
    <guid isPermaLink="false">https://www.percona.com/?p=53766</guid>
    <link>https://www.percona.com/blog/vector-distance-vector-similarity-search-in-percona-server-for-mysql/</link>
    <description>TL;DR
Percona Server for MySQL 9.7.2-2 now supports DISTANCE() for vector similarity scoring directly in SQL (COSINE, EUCLIDEAN, MANHATTAN, DOT metrics). This is the compute primitive you need to rank or filter embeddings by similarity directly in SQL. ANN indexing (e.g. HNSW, IVF) is the next milestone for fast large-scale similarity search; and this function provides the scoring layer that indexing strategies will further accelerate.
Why we’re adding this
MySQL’s native DISTANCE() and VECTOR_DISTANCE() functions are only available in HeatWave MySQL on OCI, not included in Community or Commercial MySQL, and limited to three metrics (COSINE, DOT, EUCLIDEAN). Percona is bringing the same capability to anyone running Percona Server for MySQL on any supported platform.
MySQL 9.7 already supports the VECTOR data type (TO_VECTOR() and FROM_VECTOR() functions) for storing embeddings. DISTANCE() is the natural next step: it lets you query by similarity directly in SQL, ranking or filtering rows based on vector distance, without leaving MySQL.
Background on Vectors &amp;amp; Distance Metrics
What’s a vector?
A fixed-length list of numbers, an “embedding” produced by an ML model to represent something (text, an image, a product, a user preference). The key insight: “similar” things end up with numerically close vectors, so you can compute their distance to rank or filter them.
What’s a distance metric?
A mathematical function that turns two vectors into a single number describing similarity. Here are the five metrics this release supports:

EUCLIDEAN (L2): Straight-line distance, the intuitive notion of geometric distance. General-purpose; works well when magnitude matters.
EUCLIDEAN_SQUARED: Same ranking as EUCLIDEAN, but skips the square root. Faster for comparisons and ORDER BY clauses when you only care about ordering, not the actual value.
MANHATTAN (L1): Grid-style distance (like taxicab distance on a city grid). More robust to outliers than Euclidean in some applications.
COSINE similarity: Measures the angle between two vectors. Cosine distance is computed as 1 minus the cosine similarity so that smaller value = more similar.
DOT (inner product): Raw dot product sum(a[i] * b[i]). Used by models trained specifically for dot-product similarity (e.g., some matrix-factorization systems). Normally higher values mean more similar but since it is common in the industry to multiply by -1 and flip the scale: now smaller values = more similar, in line with the other metrics above.

What’s SIMD?
Single Instruction, Multiple Data: a CPU capability that performs the same arithmetic on many numbers at once instead of one at a time. Distance math over hundreds of dimensions (embedding size) is exactly the kind of repetitive arithmetic that SIMD accelerates. The Percona implementation automatically detects and uses the best SIMD tier available on your hardware at startup, no recompilation needed per target architecture.
Meet DISTANCE() / VECTOR_DISTANCE()
Function signature:
DISTANCE(vector1, vector2, metric)
Where: – vector1 and vector2 are VECTOR data type or binary string literals (via TO_VECTOR()). – metric is a fixed literal string (case-insensitive, not a column nor an expression): ‘EUCLIDEAN’, ‘EUCLIDEAN_SQUARED’, ‘MANHATTAN’, ‘COSINE’, or ‘DOT’. – Returns a DOUBLE representing the distance/similarity score.
Synonym: VECTOR_DISTANCE() is an alias with identical behavior.
Choosing a metric: Match the metric to how your embedding model was trained. Most modern embedding models (OpenAI, Cohere, etc.) are trained with COSINE similarity, so use ‘COSINE’. If magnitude carries meaning (e.g., you’re working with raw feature vectors, not normalized embeddings), use ‘EUCLIDEAN’ or ‘EUCLIDEAN_SQUARED’. When in doubt, check your embedding model’s documentation.

All dimensions must match: Both vectors must have the same dimensionality, or the function returns an error.
If either input is NULL, the result is NULL.

Getting started: Step-by-step example
1. Create a table with vector embeddings
CREATE TABLE products (
 id INT PRIMARY KEY,
 name VARCHAR(255),
 embedding VECTOR(1536)  -- Example: 1536-dimensional embedding
);
2. Insert some embeddings
INSERT INTO products VALUES
 (1, 'Product A', TO_VECTOR('[0.1, 0.2, 0.3, ..., 0.384]')),
 (2, 'Product B', TO_VECTOR('[0.15, 0.25, 0.35, ..., 0.385]')),
 (3, 'Product C', TO_VECTOR('[0.5, 0.6, 0.7, ..., 0.800]'));
3. Query by similarity
Find the top-5 products most similar to a query embedding:SELECT id, name, DISTANCE(embedding, TO_VECTOR('[0.12, 0.22, 0.32, ..., 0.382]'), 'EUCLIDEAN') AS similarity_score
FROM products
ORDER BY similarity_score
LIMIT 5;Results ordered by highest similarity first (best/closest matches first) :id | name       | similarity_score
---|------------|------------------
2  | Product B  | 0.5432109
1  | Product A  | 0.9654321
3  | Product C  | 0.9876543
Under the hood: SIMD dispatch that adapts to your hardware
The engineering story behind the performance claim: instead of compiling the binary once for a specific CPU (with -march=native), Percona’s implementation detects the CPU’s capabilities at startup and selects the best available SIMD tier:

SSE4.2 (x86_64) or NEON (aarch64):  128-bit SIMD, ~2–3× speedup over scalar.
AVX2 (x86_64):  256-bit SIMD, ~4–6× speedup.
AVX-512F (x86_64): 512-bit SIMD, ~8–12× speedup on CPUs that support it.
SVE2 (aarch64): scalable SIMD on ARM, adapts to the CPU’s vector width.
Scalar: unoptimized fallback, works everywhere.

Dimension-aware kernel selection: Distance calculations on small vectors (&amp;lt;16 dimensions) uses the narrower 128-bit tier, because the overhead of setting up larger SIMD registers outweighs the benefit. Larger vectors automatically use the widest available tier.
Unaligned loads by design: VECTOR column data isn’t guaranteed to be cache-line aligned (and shouldn’t require alignment). All SIMD kernels use unaligned-load intrinsics as modern CPUs have identical throughput for aligned and unaligned loads when data is in cache. By always using unaligned loads, we avoid faulting on misaligned input without sacrificing performance.
Current limitations and what’s coming next
DISTANCE() is a scalar function that computes the distance between two vectors and returns a single number. Without an approximate-nearest-neighbor (ANN) index, a query like ORDER BY DISTANCE(…) LIMIT k over a large table is a full table scan: you call the distance function on every row, then sort. It’s correct and SIMD-accelerated per row, but it scales as O(n), not O(log n).
The natural next step: ANN indexing. To make large-scale similarity search fast (e.g., finding the 10 nearest neighbors in a table of 1 million vectors in milliseconds), you need an approximate-nearest-neighbor index: HNSW (Hierarchical Navigable Small World), IVF (Inverted File with refinement), or similar. These are graph-based or clustering-based structures that prune the search space and return approximate results much faster. This is the direction the vector feature is building toward, and it’s on the roadmap. For now, DISTANCE() is the scoring primitive that those indexing strategies will accelerate.
While ANN indexing delivers speed, it relies on approximate results. Should your application require exact precision instead of estimates, the DISTANCE() function is available in Percona Server for MySQL 9.7.2-2.
We want your feedback
Try it out and let us know what you think: – Report bugs or feature ideas on JIRA. – Join the conversation on Percona community forum. – Questions about usage or performance? Reach out to us.
Your feedback shapes the roadmap, especially use cases you’d like to see (e.g., specific ANN index strategies, embedding model integrations, performance tuning for your workload).
See also

VECTOR data type documentation
DISTANCE() / VECTOR_DISTANCE() reference
Getting started with vector search in Percona Server for MySQL (coming soon)
An Introduction to Vector Databases

 

Written by Catalin Besleaga. Reviewed by Dennis Kittrell and Peter Zaitsev.
Percona® is a registered trademark of Percona LLC. MySQL® is a registered trademark of Oracle Corporation.
The post DISTANCE() and VECTOR_DISTANCE(): Vector Similarity in Percona Server for MySQL 9.7 appeared first on Percona.</description>
    <content:encoded><![CDATA[<h2><b>TL;DR</b></h2>
<p><span>Percona Server for </span><span>MySQL 9.7.2-2</span><span> now supports DISTANCE() for vector similarity scoring directly in SQL (COSINE, EUCLIDEAN, MANHATTAN, DOT metrics). This is the compute primitive you need to rank or filter embeddings by similarity directly in SQL. ANN indexing (e.g. HNSW, IVF) is the next milestone for fast large-scale similarity search; and this function provides the scoring layer that indexing strategies will further accelerate.</span></p>
<h2><b>Why we’re adding this</b></h2>
<p><span>MySQL’s native </span><span>DISTANCE()</span><span> and </span><span>VECTOR_DISTANCE()</span><span> functions are only available in HeatWave MySQL on OCI, not included in Community or Commercial MySQL, and limited to three metrics (COSINE, DOT, EUCLIDEAN). Percona is bringing the same capability to anyone running Percona Server for MySQL on any supported platform.</span></p>
<p><span>MySQL 9.7 already supports the </span><span>VECTOR</span><span> data type (</span><span>TO_VECTOR()</span><span> and </span><span>FROM_VECTOR()</span><span> functions) for storing embeddings. </span><span>DISTANCE()</span><span> is the natural next step: it lets you </span><i><span>query</span></i><span> by similarity directly in SQL, ranking or filtering rows based on vector distance, without leaving MySQL.</span></p>
<h2><b>Background on Vectors &amp; Distance Metrics</b></h2>
<h3><b>What’s a vector?</b></h3>
<p><span>A fixed-length list of numbers, an “embedding” produced by an ML model to represent something (text, an image, a product, a user preference). The key insight: “similar” things end up with numerically close vectors, so you can compute their distance to rank or filter them.</span></p>
<h3><b>What’s a distance metric?</b></h3>
<p><span>A mathematical function that turns two vectors into a single number describing similarity. Here are the five metrics this release supports:</span></p>
<ul>
<li aria-level="1"><b>EUCLIDEAN (L2):</b><span> Straight-line distance, the intuitive notion of geometric distance. General-purpose; works well when magnitude matters.</span></li>
<li aria-level="1"><b>EUCLIDEAN_SQUARED:</b><span> Same ranking as EUCLIDEAN, but skips the square root. Faster for comparisons and </span><span>ORDER BY</span><span> clauses when you only care about ordering, not the actual value.</span></li>
<li aria-level="1"><b>MANHATTAN (L1):</b><span> Grid-style distance (like taxicab distance on a city grid). More robust to outliers than Euclidean in some applications.</span></li>
<li aria-level="1"><b>COSINE </b><span>similarity: Measures the angle between two vectors. Cosine distance is computed as 1 minus the cosine similarity so that smaller value = more similar.</span></li>
<li aria-level="1"><b>DOT (inner product):</b><span> Raw dot product </span><span>sum(a[i] * b[i])</span><span>. Used by models trained specifically for dot-product similarity (e.g., some matrix-factorization systems). Normally higher values mean more similar but since it is common in the industry to multiply by -1 and flip the scale: now smaller values = more similar, in line with the other metrics above.</span></li>
</ul>
<h3><b>What’s SIMD?</b></h3>
<p><span>Single Instruction, Multiple Data: a CPU capability that performs the same arithmetic on many numbers at once instead of one at a time. Distance math over hundreds of dimensions (embedding size) is exactly the kind of repetitive arithmetic that SIMD accelerates. The Percona implementation automatically detects and uses the best SIMD tier available on your hardware at startup, no recompilation needed per target architecture.</span></p>
<h2><b>Meet DISTANCE() / VECTOR_DISTANCE()</b></h2>
<p><b>Function signature:</b></p>
<p><span>DISTANCE(vector1, vector2, metric)</span></p>
<p><span>Where: – </span><span>vector1</span><span> and </span><span>vector2</span><span> are </span><span>VECTOR</span><span> data type or binary string literals (via </span><span>TO_VECTOR()</span><span>). – </span><span>metric</span><span> is a fixed literal string (case-insensitive, not a column nor an expression): </span><span>‘EUCLIDEAN’</span><span>, </span><span>‘EUCLIDEAN_SQUARED’</span><span>, </span><span>‘MANHATTAN’</span><span>, </span><span>‘COSINE’</span><span>, or </span><span>‘DOT’</span><span>. – Returns a </span><span>DOUBLE</span><span> representing the distance/similarity score.</span></p>
<p><b>Synonym:</b> <span>VECTOR_DISTANCE()</span><span> is an alias with identical behavior.</span></p>
<p><b>Choosing a metric:</b><span> Match the metric to how your embedding model was trained. Most modern embedding models (OpenAI, Cohere, etc.) are trained with COSINE similarity, so use </span><span>‘COSINE’</span><span>. If magnitude carries meaning (e.g., you’re working with raw feature vectors, not normalized embeddings), use </span><span>‘EUCLIDEAN’</span><span> or </span><span>‘EUCLIDEAN_SQUARED’</span><span>. When in doubt, check your embedding model’s documentation.</span></p>
<ul>
<li aria-level="1"><b>All dimensions must match:</b><span> Both vectors must have the same dimensionality, or the function returns an error.</span></li>
<li aria-level="1"><span>If either input is NULL, the result is NULL.</span></li>
</ul>
<h2><b>Getting started: Step-by-step example</b></h2>
<h3><b>1. Create a table with vector embeddings</b></h3>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE products (
 id INT PRIMARY KEY,
 name VARCHAR(255),
 embedding VECTOR(1536)  -- Example: 1536-dimensional embedding
);</pre><p></p>
<h3><b>2. Insert some embeddings</b></h3>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">INSERT INTO products VALUES
 (1, 'Product A', TO_VECTOR('[0.1, 0.2, 0.3, ..., 0.384]')),
 (2, 'Product B', TO_VECTOR('[0.15, 0.25, 0.35, ..., 0.385]')),
 (3, 'Product C', TO_VECTOR('[0.5, 0.6, 0.7, ..., 0.800]'));</pre><p></p>
<h3><b>3. Query by similarity</b></h3>
<p><span>Find the top-5 products most similar to a query embedding:</span></p><pre class="urvanov-syntax-highlighter-plain-tag">SELECT id, name, DISTANCE(embedding, TO_VECTOR('[0.12, 0.22, 0.32, ..., 0.382]'), 'EUCLIDEAN') AS similarity_score
FROM products
ORDER BY similarity_score
LIMIT 5;</pre><p><b>Results ordered by highest similarity first (best/closest matches first) :</b></p><pre class="urvanov-syntax-highlighter-plain-tag">id | name       | similarity_score
---|------------|------------------
2  | Product B  | 0.5432109
1  | Product A  | 0.9654321
3  | Product C  | 0.9876543</pre><p></p>
<h2><b>Under the hood: SIMD dispatch that adapts to your hardware</b></h2>
<p><span>The engineering story behind the performance claim: instead of compiling the binary once for a specific CPU (with </span><span>-march=native</span><span>), Percona’s implementation detects the CPU’s capabilities at startup and selects the best available SIMD tier:</span></p>
<ul>
<li aria-level="1"><b>SSE4.2</b><span> (x86_64) or </span><b>NEON</b><span> (aarch64):  128-bit SIMD, ~2–3× speedup over scalar.</span></li>
<li aria-level="1"><b>AVX2</b><span> (x86_64):  256-bit SIMD, ~4–6× speedup.</span></li>
<li aria-level="1"><b>AVX-512F</b><span> (x86_64): 512-bit SIMD, ~8–12× speedup on CPUs that support it.</span></li>
<li aria-level="1"><b>SVE2</b><span> (aarch64): scalable SIMD on ARM, adapts to the CPU’s vector width.</span></li>
<li aria-level="1"><b>Scalar</b><span>: unoptimized fallback, works everywhere.</span></li>
</ul>
<p><b>Dimension-aware kernel selection:</b><span> Distance calculations on small vectors (&lt;16 dimensions) uses the narrower 128-bit tier, because the overhead of setting up larger SIMD registers outweighs the benefit. Larger vectors automatically use the widest available tier.</span></p>
<p><b>Unaligned loads by design:</b> <span>VECTOR</span><span> column data isn’t guaranteed to be cache-line aligned (and shouldn’t require alignment). All SIMD kernels use unaligned-load intrinsics as modern CPUs have identical throughput for aligned and unaligned loads when data is in cache. By always using unaligned loads, we avoid faulting on misaligned input without sacrificing performance.</span></p>
<h2><b>Current limitations and what’s coming next</b></h2>
<p><span>DISTANCE()</span><span> is a scalar function that computes the distance between two vectors and returns a single number. </span><b>Without an approximate-nearest-neighbor (ANN) index, a query like </b><b>ORDER BY DISTANCE(…) LIMIT k</b><b> over a large table is a full table scan:</b><span> you call the distance function on every row, then sort. It’s correct and SIMD-accelerated per row, but it scales as O(n), not O(log n).</span></p>
<p><b>The natural next step: ANN indexing.</b><span> To make large-scale similarity search fast (e.g., finding the 10 nearest neighbors in a table of 1 million vectors in milliseconds), you need an approximate-nearest-neighbor index: HNSW (Hierarchical Navigable Small World), IVF (Inverted File with refinement), or similar. These are graph-based or clustering-based structures that prune the search space and return approximate results much faster. This is the direction the vector feature is building toward, and it’s on the roadmap. For now, </span><span>DISTANCE()</span><span> is the scoring primitive that those indexing strategies will accelerate.</span></p>
<p><span>While ANN indexing delivers speed, it relies on approximate results. Should your application require exact precision instead of estimates, the DISTANCE() function is available in Percona Server for MySQL 9.7.2-2</span><span>.</span></p>
<h2><b>We want your feedback</b></h2>
<p><span>Try it out and let us know what you think: – Report bugs or feature ideas on</span> <a href="https://perconadev.atlassian.net/jira/software/c/projects/PS/list"><b>JIRA</b></a><span>.</span><span> – Join the conversation on </span><a href="https://forums.percona.com/c/mysql-mariadb/36"><b>Percona community forum</b></a><span>. – Questions about usage or performance? <a href="https://percona.community/contribute/contact/">Reach out to us</a></span><span>.</span></p>
<p><span>Your feedback shapes the roadmap, especially use cases you’d like to see (e.g., specific ANN index strategies, embedding model integrations, performance tuning for your workload).</span></p>
<h2><b>See also</b></h2>
<ul>
<li aria-level="1"><a href="https://dev.mysql.com/doc/refman/9.7/en/vector.html"><span>VECTOR</span><span> data type documentation</span></a></li>
<li aria-level="1"><a href="https://dev.mysql.com/doc/refman/9.1/en/vector-functions.html"><span>DISTANCE()</span><span> / </span><span>VECTOR_DISTANCE()</span><span> reference</span></a></li>
<li aria-level="1"><span>Getting started with vector search in Percona Server for MySQL (coming soon)</span></li>
<li aria-level="1"><a href="https://www.percona.com/blog/an-introduction-to-vector-databases/">An Introduction to Vector Databases</a></li>
</ul>
<p> </p>
<hr>
<p><em>Written by Catalin Besleaga. Reviewed by Dennis Kittrell and Peter Zaitsev.</em><br>
<em>Percona® is a registered trademark of Percona LLC. MySQL® is a registered trademark of Oracle Corporation.</em></p>
<p>The post <a href="https://www.percona.com/blog/vector-distance-vector-similarity-search-in-percona-server-for-mysql/">DISTANCE() and VECTOR_DISTANCE(): Vector Similarity in Percona Server for MySQL 9.7</a> appeared first on <a href="https://www.percona.com/">Percona</a>.</p>]]></content:encoded>
    <pubDate>Mon, 21 Sep 2026 09:57:06 +0000</pubDate>
    <dc:creator>MySQL Performance Blog</dc:creator>
    <category>AI &amp; Vector</category>
    <category>MySQL</category>
    <category>Open Source</category>
    <category>AI</category>
    <category>vector</category>
    <category>vector_distance</category>
  </item>

  <item>
    <title>Getting VECTOR capabilities in MySQL 8.4 using VillageSQL</title>
    <guid isPermaLink="false">https://ronaldbradford.com/blog/2026-09-20-getting-vector-capabilities-in-mysql-8-4-using-villagesql/</guid>
    <link>https://ronaldbradford.com/blog/2026-09-20-getting-vector-capabilities-in-mysql-8-4-using-villagesql/</link>
    <description>For this quick verification of the 0.0.7 development branch of VillageSQL with the new vsql-vector plugin.
Recreate the VillageSQL Percona Live presentation using MySQL version 8.4 and SVECTOR. Demonstrate a more detailed example using SVECTOR(1024) string embedded data.</description>
    <pubDate>Sun, 20 Sep 2026 00:00:00 +0000</pubDate>
    <dc:creator>Ronald Bradford</dc:creator>
  </item>

  <item>
    <title>MySQL Community Engagement in Brazil and Europe </title>
    <guid isPermaLink="false">50c656ce1f793097349980f665893457</guid>
    <link>https://blogs.oracle.com/mysql/mysql-community-engagement-in-brazil-and-europe</link>
    <description>This September and October, Heather VanCura will meet with MySQL users, contributors, developers, DBAs, customers, and community leaders across Brazil and Europe. With more than 30 years of innovation behind it, the MySQL community is entering an important new phase. The focus is on expanding engagement, collaboration, and contributions while increasing and driving innovation and unification of the ecosystem. The tour will […]</description>
    <pubDate>Fri, 18 Sep 2026 17:13:57 +0000</pubDate>
    <dc:creator>Oracle MySQL Group</dc:creator>
    <category>MySQL</category>
    <category>MySQL Community</category>
    <category>mysql</category>
    <category>mysqlcommunity</category>
  </item>

  <item>
    <title>Using DuckDB inside MySQL</title>
    <guid isPermaLink="false">6aaade83ae8663000196d8dd</guid>
    <link>https://villagesql.com/blog/duckdb/</link>
    <description>We are pleased to announce a new extension for VillageSQL Server that enables running DuckDB queries from within MySQL and joining those results with MySQL query results. DuckDB has emerged as the analytical engine of choice for fast querying of data formats such as Parquet. It excels with analytics queries because of its columnar storage, vectorized execution, and embedded architecture. Applications often need to combine the results of analytical queries with operational results, though. There are multiple ways to do this, but many are suboptimal when the query's results need to be returned to an application that is connected to an operational database such as MySQL.
The new vsql-duckdb extension from VillageSQL solves this by embedding DuckDB inside MySQL, keeping your existing database connection and SQL as the interaction point. It embeds DuckDB inside VillageSQL Server and exposes three functions that pass query text to it, which is similar to what pg_duckdb offers for PostgreSQL.
VillageSQL is the innovation platform for MySQL that adds an extension framework (VEF), similar to PostgreSQL's extension framework, to enable permissionless innovation. Instead of waiting for a feature to be implemented in a few years in a future version of MySQL, new functionality can be dynamically added to a version of MySQL you run today.
Three functions
The extension has three functions. Two of the functions take DuckDB query text as a string and differ only in what they hand back. duckdb_scalar() returns the first value of the first row, which covers counts, sums, and anything else that is a single answer. duckdb_query() returns the whole result as a JSON array with one object per row, and MySQL's JSON_TABLE turns that array back into rows you can join. The third function, duckdb_status(), takes no query at all and reports which DuckDB version is compiled in, which file readers the bundle was built with, and whether your object storage credential loaded.
Installing
To get started, build the extension from source (https://github.com/villagesql/vsql-duckdb). The build compiles DuckDB inside it, so it takes a few minutes the first time. The extension runs on VillageSQL Server 0.0.6 or newer. The examples in this post that pass a result into JSON_EXTRACT or JSON_TABLE need a server newer than 0.0.6 (0.0.7-dev as of this writing), which hands VEF function results to MySQL's JSON functions as utf8mb4 text. On 0.0.6, wrap the call in CONVERT(... USING utf8mb4) first. Follow the build instructions on the Readme. The install step writes vsql_duckdb.veb (VillageSQL Extension Bundle) into the directory the server loads extensions from. If you want to confirm where that is, ask the server with SHOW VARIABLES LIKE 'veb_dir'.
Next, install the extension from SQL. The extension declares two preview capabilities, sys_var and keyring, so the server has to allow preview extensions first. SET PERSIST takes effect immediately, so these two statements run back to back with no restart between them:
SET PERSIST vsql_allow_preview_extensions = ON;
INSTALL EXTENSION vsql_duckdb;

Ask duckdb_status() which readers the build gave you:
SELECT JSON_EXTRACT(duckdb_status(), '$.readers') AS readers;

[&quot;core_functions&quot;, &quot;httpfs&quot;, &quot;json&quot;, &quot;parquet&quot;]

httpfs is the one that makes object storage work. It handles both s3:// and https:// paths.
Querying a remote file
Point duckdb_scalar() at a public 127 MB Parquet file, with no credentials and nothing copied onto your server, and you get an answer back:
SELECT duckdb_scalar('SELECT count(*) FROM read_parquet(''https://blobs.duckdb.org/data/taxi_2019_04.parquet'')');

7433139

A Parquet file records its own row count in a footer. DuckDB fetches that footer over HTTP and reads the count straight out of it, so it never reads the trip data at all.
A query that reads real column values has to pull those columns across the network first, so it takes longer than a count does. vsql_duckdb.timeout_ms bounds how long the calling connection waits, and it defaults to 30 seconds. A query that runs past it stops with an error. You can raise the limit, and you can also cap how much memory DuckDB takes, how many worker threads it starts, and how large a result one call may return. The README lists every setting with its default and range.
Pointing it at your own bucket
Reading a private bucket takes a region, an access key id, and somewhere to keep the secret access key. No setting holds that secret. The extension reads it from the server's keyring through VEF's keyring capability, and the settings only name the entry to look for.
Reading is all it does there, on purpose. VEF can write a key as well, and a key written that way is unreadable from SQL by anyone, which is what a server credential wants. The catch is that extension functions cannot be granted per user, so a setter would let every user of the server replace the credential. The operator stores the key instead, which needs a keyring component loaded and the keyring_udf plugin:
SELECT keyring_key_store('duckdb_s3_secret', 'AES', 'the-secret-access-key');

SET PERSIST vsql_duckdb.s3_region = 'eu-north-1';
SET PERSIST vsql_duckdb.s3_key_id = 'AKIAEXAMPLE';
SET PERSIST vsql_duckdb.s3_secret_keyring_id = 'duckdb_s3_secret';
SET PERSIST vsql_duckdb.s3_secret_keyring_auth_id = 'root@localhost';

A key stored from SQL belongs to the account that stored it, so s3_secret_keyring_auth_id has to name that account in full user@host form. Call duckdb_status() afterwards and it tells you whether the credential loaded. Google Cloud Storage is configured through the same settings with an HMAC key, and the README covers it along with S3-compatible stores like MinIO.
From there an s3:// path behaves exactly like the public URL above:
SELECT duckdb_scalar('SELECT count(*) FROM read_parquet(''s3://sales/2026/*.parquet'')');

Joining a dataset to a real table
DuckDB has no view of your InnoDB tables, and a query that names one fails in DuckDB's catalog rather than in MySQL. So you do the join in MySQL. duckdb_query hands back a JSON array, JSON_TABLE unpacks that array into rows, and those rows join against a real table like any others.
In the example below, the Parquet side is a synthetic sales dataset, 5 million rows over four files under /data/sales/ in the Hive layout that hive_partitioning = true reads. The files sit on the server's own disk, and the extension refuses local paths until you allow them:
SET PERSIST vsql_duckdb.allow_local_files = ON;

regions is an ordinary InnoDB table with one row per city, holding the region it sits in and the manager who owns it. DuckDB rolls up the files and MySQL joins the totals:
SELECT r.region, r.manager, t.orders, t.revenue
FROM JSON_TABLE(
  duckdb_query('SELECT city, count(*) AS orders, sum(amount) AS revenue
                FROM read_parquet(''/data/sales/**/*.parquet'', hive_partitioning = true)
                GROUP BY city'),
  '$[*]' COLUMNS (city    VARCHAR(64) PATH '$.city',
                  orders  BIGINT      PATH '$.orders',
                  revenue BIGINT      PATH '$.revenue')) AS t
JOIN regions r ON r.city = t.city
ORDER BY t.revenue DESC;

+--------+---------+---------+----------+
| region | manager | orders  | revenue  |
+--------+---------+---------+----------+
| east   | ana     | 1250000 | 61249754 |
| west   | dia     | 1250000 | 61249734 |
| west   | ben     | 1250000 | 61249715 |
| north  | cai     | 1250000 | 61249676 |
+--------+---------+---------+----------+

Only four grouped rows cross between the two engines, because DuckDB does the counting and summing before it hands anything over. Keep its side of the work to counts, sums, and rollups, and the JSON string stays small. Ask it for raw rows instead and the array grows until it reaches the one megabyte result cap.
What it does not do yet
This is an initial version of the duckdb extension for VillageSQL Server. You always write the DuckDB query yourself. There is no CREATE FOREIGN TABLE that makes a Parquet file look like a MySQL table, no pushdown of a MySQL WHERE clause into DuckDB, and no routing of ordinary SQL to DuckDB, so every call is an explicit duckdb_query('...'). DuckDB cannot read your InnoDB tables either, which is why the join belongs in the outer query. A result larger than one megabyte raises an error rather than coming back cut short, because a truncated JSON array loses its closing bracket and stops parsing. Four of DuckDB's components are built in today: parquet, json, httpfs, and core_functions.
There are alternative approaches to connecting MySQL and DuckDB too. DuckDB's own mysql extension will ATTACH your database and join an InnoDB table to a Parquet file in one query. dbtrail reads the binlog and archives it as Parquet for DuckDB to query. Alibaba's AliSQL embeds DuckDB in mysqld as a pluggable storage engine, where ALTER TABLE ... ENGINE=DuckDB converts an existing table to columnar storage.
The first two put a Parquet file within reach, but you have to query from somewhere other than your database. You connect to DuckDB, or you query what a pipeline already copied out. AliSQL does run inside MySQL, but it only stores tables you have already loaded, so a Parquet file sitting in a bucket stays out of reach. In none of the three can an application connected to your MySQL server ask that Parquet file a question.
The settings reference, the pg_duckdb migration table, and the full limitations list are in the vsql-duckdb README. To get VillageSQL Server, start at villagesql.com.
Please let us know your feedback. You can find us on Discord or on GitHub Issues.</description>
    <content:encoded><![CDATA[<img src="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/images/2026/09/duckdb_villagesql.jpeg" alt="Using DuckDB inside MySQL"><p>We are pleased to announce a <a href="https://github.com/villagesql/vsql-duckdb">new extension for VillageSQL Server</a> that enables running DuckDB queries from within MySQL and joining those results with MySQL query results. DuckDB has emerged as the analytical engine of choice for fast querying of data formats such as Parquet. It excels with analytics queries because of its columnar storage, vectorized execution, and embedded architecture. Applications often need to combine the results of analytical queries with operational results, though. There are multiple ways to do this, but many are suboptimal when the query's results need to be returned to an application that is connected to an operational database such as MySQL.</p>
<p>The new <a href="https://github.com/villagesql/vsql-duckdb">vsql-duckdb</a> extension from VillageSQL solves this by embedding DuckDB inside MySQL, keeping your existing database connection and SQL as the interaction point. It embeds DuckDB inside VillageSQL Server and exposes three functions that pass query text to it, which is similar to what <a href="https://github.com/duckdb/pg_duckdb">pg_duckdb</a> offers for PostgreSQL.</p>
<p>VillageSQL is the innovation platform for MySQL that adds an extension framework (VEF), similar to PostgreSQL's extension framework, to enable permissionless innovation. Instead of waiting for a feature to be implemented in a few years in a future version of MySQL, new functionality can be dynamically added to a version of MySQL you run today.</p>
<h2>Three functions</h2>
<p>The extension has three functions. Two of the functions take DuckDB query text as a string and differ only in what they hand back. <code>duckdb_scalar()</code> returns the first value of the first row, which covers counts, sums, and anything else that is a single answer. <code>duckdb_query()</code> returns the whole result as a JSON array with one object per row, and MySQL's <code>JSON_TABLE</code> turns that array back into rows you can join. The third function, <code>duckdb_status()</code>, takes no query at all and reports which DuckDB version is compiled in, which file readers the bundle was built with, and whether your object storage credential loaded.</p>
<h2>Installing</h2>
<p>To get started, build the extension from source (<a href="https://github.com/villagesql/vsql-duckdb">https://github.com/villagesql/vsql-duckdb</a>). The build compiles DuckDB inside it, so it takes a few minutes the first time. The extension runs on VillageSQL Server 0.0.6 or newer. The examples in this post that pass a result into <code>JSON_EXTRACT</code> or <code>JSON_TABLE</code> need a server newer than 0.0.6 (0.0.7-dev as of this writing), which hands VEF function results to MySQL's JSON functions as <code>utf8mb4</code> text. On 0.0.6, wrap the call in <code>CONVERT(... USING utf8mb4)</code> first. Follow the build instructions on the Readme. The install step writes <code>vsql_duckdb.veb</code> (VillageSQL Extension Bundle) into the directory the server loads extensions from. If you want to confirm where that is, ask the server with <code>SHOW VARIABLES LIKE 'veb_dir'</code>.</p>
<p>Next, install the extension from SQL. The extension declares two preview capabilities, <code>sys_var</code> and <code>keyring</code>, so the server has to allow preview extensions first. <code>SET PERSIST</code> takes effect immediately, so these two statements run back to back with no restart between them:</p>
<pre><code class="language-sql">SET PERSIST vsql_allow_preview_extensions = ON;
INSTALL EXTENSION vsql_duckdb;
</code></pre>
<p>Ask <code>duckdb_status()</code> which readers the build gave you:</p>
<pre><code class="language-sql">SELECT JSON_EXTRACT(duckdb_status(), '$.readers') AS readers;
</code></pre>
<pre><code>["core_functions", "httpfs", "json", "parquet"]
</code></pre>
<p><code>httpfs</code> is the one that makes object storage work. It handles both <code>s3://</code> and <code>https://</code> paths.</p>
<h2>Querying a remote file</h2>
<p>Point <code>duckdb_scalar()</code> at a public 127 MB Parquet file, with no credentials and nothing copied onto your server, and you get an answer back:</p>
<pre><code class="language-sql">SELECT duckdb_scalar('SELECT count(*) FROM read_parquet(''https://blobs.duckdb.org/data/taxi_2019_04.parquet'')');
</code></pre>
<pre><code>7433139
</code></pre>
<p>A Parquet file records its own row count in a footer. DuckDB fetches that footer over HTTP and reads the count straight out of it, so it never reads the trip data at all.</p>
<p>A query that reads real column values has to pull those columns across the network first, so it takes longer than a count does. <code>vsql_duckdb.timeout_ms</code> bounds how long the calling connection waits, and it defaults to 30 seconds. A query that runs past it stops with an error. You can raise the limit, and you can also cap how much memory DuckDB takes, how many worker threads it starts, and how large a result one call may return. The <a href="https://github.com/villagesql/vsql-duckdb#configuration">README</a> lists every setting with its default and range.</p>
<h2>Pointing it at your own bucket</h2>
<p>Reading a private bucket takes a region, an access key id, and somewhere to keep the secret access key. No setting holds that secret. The extension reads it from the server's keyring through VEF's keyring capability, and the settings only name the entry to look for.</p>
<p>Reading is all it does there, on purpose. VEF can write a key as well, and a key written that way is unreadable from SQL by anyone, which is what a server credential wants. The catch is that extension functions cannot be granted per user, so a setter would let every user of the server replace the credential. The operator stores the key instead, which needs a keyring component loaded and the <code>keyring_udf</code> plugin:</p>
<pre><code class="language-sql">SELECT keyring_key_store('duckdb_s3_secret', 'AES', 'the-secret-access-key');

SET PERSIST vsql_duckdb.s3_region = 'eu-north-1';
SET PERSIST vsql_duckdb.s3_key_id = 'AKIAEXAMPLE';
SET PERSIST vsql_duckdb.s3_secret_keyring_id = 'duckdb_s3_secret';
SET PERSIST vsql_duckdb.s3_secret_keyring_auth_id = 'root@localhost';
</code></pre>
<p>A key stored from SQL belongs to the account that stored it, so <code>s3_secret_keyring_auth_id</code> has to name that account in full <code>user@host</code> form. Call <code>duckdb_status()</code> afterwards and it tells you whether the credential loaded. Google Cloud Storage is configured through the same settings with an HMAC key, and the README covers it along with S3-compatible stores like MinIO.</p>
<p>From there an <code>s3://</code> path behaves exactly like the public URL above:</p>
<pre><code class="language-sql">SELECT duckdb_scalar('SELECT count(*) FROM read_parquet(''s3://sales/2026/*.parquet'')');
</code></pre>
<h2>Joining a dataset to a real table</h2>
<p>DuckDB has no view of your InnoDB tables, and a query that names one fails in DuckDB's catalog rather than in MySQL. So you do the join in MySQL. <code>duckdb_query</code> hands back a JSON array, <code>JSON_TABLE</code> unpacks that array into rows, and those rows join against a real table like any others.</p>
<p>In the example below, the Parquet side is a synthetic sales dataset, 5 million rows over four files under <code>/data/sales/</code> in the Hive layout that <code>hive_partitioning = true</code> reads. The files sit on the server's own disk, and the extension refuses local paths until you allow them:</p>
<pre><code class="language-sql">SET PERSIST vsql_duckdb.allow_local_files = ON;
</code></pre>
<p><code>regions</code> is an ordinary InnoDB table with one row per city, holding the region it sits in and the manager who owns it. DuckDB rolls up the files and MySQL joins the totals:</p>
<pre><code class="language-sql">SELECT r.region, r.manager, t.orders, t.revenue
FROM JSON_TABLE(
  duckdb_query('SELECT city, count(*) AS orders, sum(amount) AS revenue
                FROM read_parquet(''/data/sales/**/*.parquet'', hive_partitioning = true)
                GROUP BY city'),
  '$[*]' COLUMNS (city    VARCHAR(64) PATH '$.city',
                  orders  BIGINT      PATH '$.orders',
                  revenue BIGINT      PATH '$.revenue')) AS t
JOIN regions r ON r.city = t.city
ORDER BY t.revenue DESC;
</code></pre>
<pre><code>+--------+---------+---------+----------+
| region | manager | orders  | revenue  |
+--------+---------+---------+----------+
| east   | ana     | 1250000 | 61249754 |
| west   | dia     | 1250000 | 61249734 |
| west   | ben     | 1250000 | 61249715 |
| north  | cai     | 1250000 | 61249676 |
+--------+---------+---------+----------+
</code></pre>
<p>Only four grouped rows cross between the two engines, because DuckDB does the counting and summing before it hands anything over. Keep its side of the work to counts, sums, and rollups, and the JSON string stays small. Ask it for raw rows instead and the array grows until it reaches the one megabyte result cap.</p>
<h2>What it does not do yet</h2>
<p>This is an initial version of the duckdb extension for VillageSQL Server. You always write the DuckDB query yourself. There is no <code>CREATE FOREIGN TABLE</code> that makes a Parquet file look like a MySQL table, no pushdown of a MySQL <code>WHERE</code> clause into DuckDB, and no routing of ordinary SQL to DuckDB, so every call is an explicit <code>duckdb_query('...')</code>. DuckDB cannot read your InnoDB tables either, which is why the join belongs in the outer query. A result larger than one megabyte raises an error rather than coming back cut short, because a truncated JSON array loses its closing bracket and stops parsing. Four of DuckDB's components are built in today: <code>parquet</code>, <code>json</code>, <code>httpfs</code>, and <code>core_functions</code>.</p>
<p>There are alternative approaches to connecting MySQL and DuckDB too. DuckDB's own <a href="https://github.com/duckdb/duckdb-mysql"><code>mysql</code> extension</a> will <code>ATTACH</code> your database and join an InnoDB table to a Parquet file in one query. <a href="https://percona.community/blog/2026/08/26/duckdb-speed-on-mysql-without-a-new-storage-engine/">dbtrail</a> reads the binlog and archives it as Parquet for DuckDB to query. Alibaba's <a href="https://github.com/alibaba/AliSQL/blob/master/wiki/duckdb/duckdb.md">AliSQL</a> embeds DuckDB in mysqld as a pluggable storage engine, where <code>ALTER TABLE ... ENGINE=DuckDB</code> converts an existing table to columnar storage.</p>
<p>The first two put a Parquet file within reach, but you have to query from somewhere other than your database. You connect to DuckDB, or you query what a pipeline already copied out. AliSQL does run inside MySQL, but it only stores tables you have already loaded, so a Parquet file sitting in a bucket stays out of reach. In none of the three can an application connected to your MySQL server ask that Parquet file a question.</p>
<p>The settings reference, the pg_duckdb migration table, and the full limitations list are in the <a href="https://github.com/villagesql/vsql-duckdb">vsql-duckdb README</a>. To get VillageSQL Server, start at <a href="https://villagesql.com/">villagesql.com</a>.</p>
<p>Please let us know your feedback. You can find us on <a href="https://discord.gg/KSr6whd3Fr">Discord</a> or on <a href="https://github.com/villagesql/villagesql-server/issues">GitHub Issues</a>.</p>]]></content:encoded>
    <pubDate>Thu, 17 Sep 2026 13:00:08 +0000</pubDate>
    <dc:creator>VillageSQL</dc:creator>
  </item>

  <item>
    <title>Group Replication Beyond a Single Cluster: DC-DR with Percona (PS MySQL) Operator</title>
    <guid isPermaLink="false">https://www.percona.com/?p=53589</guid>
    <link>https://www.percona.com/blog/group-replication-beyond-a-single-cluster-dc-dr-with-percona-ps-mysql-operator/</link>
    <description>A while ago, we discussed the cross-site replication feature of the Percona PXC operator. Recently, a similar cross-site replication feature was introduced in the Percona (PS MySQL) operator v1.2.0, a topology based on Group Replication/InnoDB Cluster.
In this blog post, we will explore how to add a DR Cluster to an existing DC Cluster to form a ClusterSet environment, which provides a seamless switchover between DC/DR members. The good part is that all the complexity and configuration will be managed by the Percona operator, with very few setup steps required.
Group Replication (DC-DR) using Innodb ClusterSet
 
Let’s discuss how we can implement a cross-site replication (DC-DR) topology across two separate database clusters.
Environment used for Demonstration

Two separate database clusters (pscluster1 and pscluster2) are deployed within a single GCP/Kubernetes environment, each managed by a dedicated operator.
Percona Operator for MySQL( based on Percona Server for MySQL) has a version. 1.2.0
MySQL version 8.4.10.
Network connectivity between DC/DR components.

DC (pscluster1) configuration
Step 1:  Deploying the cluster on the DC side.shell&amp;gt; kubectl get ps -n ps
NAME          REPLICATION         ENDPOINT                 STATE   MYSQL   ORCHESTRATOR   HAPROXY   ROUTER   AGE
ps-cluster1   group-replication   ps-cluster1-haproxy.ps   ready   3                      3                  28mshell&amp;gt; kubectl get pods -n ps
NAME                                             READY   STATUS    RESTARTS   AGE
percona-server-mysql-operator-6b78d5f68c-bzg6r   1/1     Running   0          30m
ps-cluster1-haproxy-0                            2/2     Running   0          28m
ps-cluster1-haproxy-1                            2/2     Running   0          27m
ps-cluster1-haproxy-2                            2/2     Running   0          27m
ps-cluster1-mysql-0                              2/2     Running   0          29m
ps-cluster1-mysql-1                              2/2     Running   0          28m
ps-cluster1-mysql-2                              2/2     Running   0          26mStep 2: Retrieve the DC’s InnoDB cluster name, which will be required later to construct the ClusterSet.shell&amp;gt; kubectl get ps ps-cluster1 -n ps -o jsonpath='{.status.innodbClusterName}{&quot;\n&quot;}'
pscluster1Step 3: Get a service endpoint which is used when setting up the ClusterSet environment. We will use ps-cluster1-mysql-primary, which is mapped to the current Primary node in the existing cluster.shell&amp;gt; kubectl get services -n ps

NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                           AGE
ps-cluster1-haproxy         ClusterIP   34.118.225.215   &amp;lt;none&amp;gt;        3306/TCP,3307/TCP,3309/TCP,33060/TCP,33062/TCP    29m
ps-cluster1-mysql           ClusterIP   None             &amp;lt;none&amp;gt;        3306/TCP,33062/TCP,33060/TCP,6450/TCP,33061/TCP   29m
ps-cluster1-mysql-primary   ClusterIP   34.118.229.137   &amp;lt;none&amp;gt;        3306/TCP,33062/TCP,33060/TCP,6450/TCP,33061/TCP   29m
ps-cluster1-mysql-proxy     ClusterIP   None             &amp;lt;none&amp;gt;        3306/TCP,33062/TCP,33060/TCP,6450/TCP,33061/TCP   29m
ps-cluster1-mysql-unready   ClusterIP   None             &amp;lt;none&amp;gt;        3306/TCP,33062/TCP,33060/TCP,6450/TCP,33061/TCP   29mStep 4: The DR site should have the same credentials as DC. We need to export the DC secret containing the credentials and import it into the DR cluster.shell&amp;gt; kubectl get secret ps-cluster1-secrets -n ps -o yaml &amp;gt; source-secret.yamlWe also need to perform a couple of cleanups in the exported secret file.

Remove the annotations, creationTimestamp, resourceVersion, selfLink, and uid metadata fields.
As per the requirement, change the namespace/instance and other unwanted details as per your replica site.

E.g,shell&amp;gt; yq eval 'del(.metadata.ownerReferences, .metadata.annotations, .metadata.creationTimestamp, .metadata.resourceVersion, .metadata.selfLink, .metadata.uid)' source-secret.yaml &amp;gt; replica-secret.yamlshell&amp;gt; yq eval '.metadata.namespace = &quot;ps-dr&quot;' -i replica-secret.yamlshell&amp;gt; sed -i '' 's/ps-cluster1/ps-cluster2/g' replica-secret.yamlThe final ready secret file looks like this. apiVersion: v1
data:
  clusterset: XVpMQz0+LEVDe3kyQGR6TQ==
  heartbeat: YmFONW1KTE8oLTxdVVB7Kg==
  monitor: UC1oOHokRksoMnpSMWsmJg==
  operator: SDMrblpSQm0yRHdhV3dteX0=
  orchestrator: cEVqMEUlaGp5fShudyZURHtl
  replication: ZjldZlkwa3ZwNWU8KVpsag==
  root: e3BycEl+LlcrPFVUUTwpaG4=
  xtrabackup: eFhNNENIRF4wJXZ7bjxbKi11ag==
kind: Secret
metadata:
  labels:
    app.kubernetes.io/component: database
    app.kubernetes.io/instance: ps-cluster2
    app.kubernetes.io/managed-by: percona-server-mysql-operator
    app.kubernetes.io/name: mysql
    app.kubernetes.io/part-of: percona-server
  name: ps-cluster2-secrets
  namespace: ps-dr
type: Opaque

DR (pscluster2) Cluster configuration
Step 1: First, we need to import the modified secret file replica-secret.yaml and initialise the DR cluster.shell&amp;gt; kubectl apply -f deploy/replica-secret.yaml -n ps-dr
secret/ps-cluster2-secrets createdshell&amp;gt; kubectl get secrets -n ps-dr
NAME                      TYPE     DATA   AGE
...
ps-cluster2-secrets   Opaque   8      12s
...Also, we need to make some modifications to the custom resource file cr.yaml to initialise the DR cluster.metadata:
  finalizers:
    - percona.com/delete-mysql-pods-in-order
#    - percona.com/delete-ssl
#    - percona.com/delete-mysql-pvc
  name: ps-cluster2

spec:
  crVersion: 1.2.0
  secretsName: ps-cluster2-secretsmysql:
    clusterType: group-replication
    size: 3
    image: percona/percona-server:8.4.10-10.1
    bootstrap:
      mode: manualshell&amp;gt; kubectl apply -f deploy/cr.yaml -n psNote: We set spec.mysql.bootstrap.mode to manual so Pod mysql-0 does not form a Group Replication group until the ClusterSet adopts it and references the secret file we copied from the DC.
It is also expected that after applying the Custom Resource file cr.yaml, the Pod mysql-0 starts but stays NotReady. The cluster ps-cluster2 reports the Initialising state and the AwaitingExternalBootstrap condition in status. Pods mysql-1 and mysql-2 do not start until Pod mysql-0  joins the Group Replication.
Once we deploy the custom resource, we can notice the status below. Please note that the complete Pods will be ready only once DR successfully syncs and joins the DC cluster. shell&amp;gt; kubectl get ps -n ps-dr
NAME          REPLICATION         ENDPOINT                    STATE          MYSQL   ORCHESTRATOR   HAPROXY   ROUTER   AGE
ps-cluster2   group-replication   ps-cluster2-haproxy.ps-dr   initializing                                             17mshell&amp;gt; kubectl get pods -n ps-dr
NAME                                             READY   STATUS    RESTARTS   AGE
percona-server-mysql-operator-6b78d5f68c-4gsm9   1/1     Running   0          28m
ps-cluster2-mysql-0Step 2: We need to note down the DR InnoDB cluster name, which will later be used by the ClusterSet.shell&amp;gt; kubectl get ps ps-cluster2 -n ps -o jsonpath='{.status.innodbClusterName}{&quot;\n&quot;}'
pscluster2Step 3: We need to identify the DR cluster endpoint that is reachable from the DC. This endpoint will be used to feed the clone and perform other management operations.
We will use the local FQDN ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr.svc.cluster.local of the ps-cluster2-mysql-0 Pod, which is currently initialised and awaiting the ready state.shell&amp;gt; kubectl get pods -n ps-dr
NAME                                             READY   STATUS    RESTARTS   AGE
percona-server-mysql-operator-6b78d5f68c-4gsm9   1/1     Running   0          48m
ps-cluster2-mysql-0                              1/2     Running   0          37mBy connecting to the DC Primary Pod, we can verify cross-communication between the DC and the DR.shell&amp;gt; kubectl exec -it ps-cluster1-mysql-0 -n ps -- shsh-5.1$ curl -v ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr.svc.cluster.local:3306
*   Trying 10.60.1.7:3306...
* Connected to ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr.svc.cluster.local (10.60.1.7) port 3306 (#0)
&amp;gt; GET / HTTP/1.1
&amp;gt; Host: ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr.svc.cluster.local:3306
&amp;gt; User-Agent: curl/7.76.1
&amp;gt; Accept: */*
Data Restoration Part
By default, when the DR site initialises and attempts to join the ClusterSet, it receives data from the source/DC via the clone recovery method.
The Operator uses MySQL Shell to create a physical snapshot of the dataset from the Source/DC and transfer it to the DR Replica.
Alternatively, we can manually perform a backup and restore on the target cluster. This strategy helps when integrating the DR site into the ClusterSet, as pre-populating the data and GTID history allows it to apply only delta changes rather than performing a full initial synchronisation. For large datasets or to prevent excessive load and performance degradation on the donor node, a backup-and-restore approach can be considered.
Here, we will use the clone recovery method to sync the DR cluster.
Next, we will deploy the ClusterSet configurations so that DR joins the DC cluster via a data clone and connects via Asynchronous Replication to stay in sync.
Below is the clusterset.yaml file where we pass the various information like (Secrets, DC/DR Endpoints, Cluster Name etc) , which we fetched in some of the above steps earlier.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.ps.svc.cluster.local
        port: 3306
    - innodbClusterName: pscluster2
      endpoints:
      - host: ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr.svc.cluster.local
        port: 3306
  mysqlshellRunner:
    image: percona/percona-server:8.4.10-10.1We will apply the changes only to the DC node.kubectl apply -f deploy/clusterset.yamlPlease note: Member “ps-cluster2-mysql-0” on the DR side will connect to the DC via asynchronous replication. While the local members of the DR will join via the Group Replication mechanism. 
This action will also start a separate backend job on DC, which creates an associated Pod to perform the ClusterSet activity.shell&amp;gt; kubectl get jobs -n ps
NAME                                    STATUS    COMPLETIONS   DURATION   AGE
my-cluster-set-pscluster2-add-replica   Complete             1/1           23s        53s….shell&amp;gt; kubectl get pods -n ps
NAME                                             READY   STATUS    RESTARTS   AGE
my-cluster-set-pscluster2-add-replica-wk7xd      1/1     Running             0          3sAs soon as the process completes successfully, the job and associated Pod will be removed from the list.
There is one extra pod that can be noticed, and it persists. This provides a utility/client for running MySQL Shell commands against the MySQL ClusterSet.shell&amp;gt; kubectl get pods -n ps
NAME                                             READY   STATUS    RESTARTS   AGE
my-cluster-set-runner-5d68fbcc74-svmpw           1/1     Running   0          10hIf the job fails or errors occur, we need to investigate the exact problem using the information below.kubectl describe job &amp;lt;jobname&amp;gt;
kubectl describe pod &amp;lt;add-replica pod&amp;gt;
kubectl logs &amp;lt;add-replica pod&amp;gt;Finally, we can check the Pod status on the DR cluster. It will now reflect all MySQL and haproxy pods in the fully completed/ready state.  shell&amp;gt; kubectl get pods -n ps-dr
NAME                                             READY   STATUS    RESTARTS      AGE
percona-server-mysql-operator-6b78d5f68c-4gsm9   1/1     Running   0             11h
ps-cluster2-haproxy-0                            2/2     Running   0             36m
ps-cluster2-haproxy-1                            2/2     Running   0             36m
ps-cluster2-haproxy-2                            2/2     Running   0             36m
ps-cluster2-mysql-0                              2/2     Running   1 (37m ago)   11h
ps-cluster2-mysql-1                              2/2     Running   1 (36m ago)   36m
ps-cluster2-mysql-2                              2/2     Running   1 (34m ago)   35m
Verification of ClusterSet completion
We can check the ClusterSet information below if it has been processed successfully without any errors.shell&amp;gt; kubectl get ps-clusterset my-cluster-set -n ps
NAME             PRIMARY      ENDPOINT                                        READY
my-cluster-set   pscluster1   ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306   Trueshell&amp;gt; kubectl get ps-clusterset my-cluster-set -n ps -o yaml
apiVersion: ps.percona.com/v1
kind: PerconaServerMySQLClusterSet
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {&quot;apiVersion&quot;:&quot;ps.percona.com/v1&quot;,&quot;kind&quot;:&quot;PerconaServerMySQLClusterSet&quot;,&quot;metadata&quot;:{&quot;annotations&quot;:{},&quot;finalizers&quot;:[&quot;percona.com/clusterset-dissolve&quot;],&quot;name&quot;:&quot;my-cluster-set&quot;,&quot;namespace&quot;:&quot;ps&quot;},&quot;spec&quot;:{&quot;clusters&quot;:[{&quot;endpoints&quot;:[{&quot;host&quot;:&quot;ps-cluster1-mysql-primary.ps.svc.cluster.local&quot;,&quot;port&quot;:3306}],&quot;innodbClusterName&quot;:&quot;pscluster1&quot;},{&quot;endpoints&quot;:[{&quot;host&quot;:&quot;ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr.svc.cluster.local&quot;,&quot;port&quot;:3306}],&quot;innodbClusterName&quot;:&quot;pscluster2&quot;}],&quot;createReplicaClusterOptions&quot;:{&quot;recoveryMethod&quot;:&quot;clone&quot;},&quot;credentialsSecret&quot;:{&quot;key&quot;:&quot;clusterset&quot;,&quot;name&quot;:&quot;ps-cluster1-secrets&quot;},&quot;mysqlshellRunner&quot;:{&quot;image&quot;:&quot;percona/percona-server:8.4.10-10.1&quot;},&quot;primaryCluster&quot;:&quot;pscluster1&quot;,&quot;sslMode&quot;:&quot;AUTO&quot;}}
  creationTimestamp: &quot;2026-09-11T05:20:08Z&quot;
  finalizers:
  - percona.com/clusterset-dissolve
  generation: 5
  name: my-cluster-set
  namespace: ps
  resourceVersion: &quot;1789143099049519007&quot;
  uid: 6cfdce40-e1e0-47c8-bdae-1cd847938f29
spec:
  clusters:
  - endpoints:
    - host: ps-cluster1-mysql-primary.ps.svc.cluster.local
      port: 3306
    innodbClusterName: pscluster1
  - endpoints:
    - host: ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr.svc.cluster.local
      port: 3306
    innodbClusterName: pscluster2
  createReplicaClusterOptions:
    recoveryMethod: clone
  credentialsSecret:
    key: clusterset
    name: ps-cluster1-secrets
  mysqlshellRunner:
    image: percona/percona-server:8.4.10-10.1
  primaryCluster: pscluster1
  sslMode: AUTO
status:
  clusters:
    pscluster1:
      clusterRole: PRIMARY
      globalStatus: OK
      primary: ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306
    pscluster2:
      clusterRole: REPLICA
      globalStatus: OK
      primary: &quot;&quot;
  conditions:
  - lastTransitionTime: &quot;2026-09-11T05:25:07Z&quot;
    message: &quot;&quot;
    reason: DeploymentReady
    status: &quot;True&quot;
    type: MySQLShellRunnerReady
  - lastTransitionTime: &quot;2026-09-11T05:25:10Z&quot;
    message: &quot;&quot;
    reason: ClusterSetBootstrapped
    status: &quot;True&quot;
    type: ClusterSetBootstrapped
  - lastTransitionTime: &quot;2026-09-11T05:25:13Z&quot;
    message: All Clusters available.
    reason: ClusterSetHealthy
    status: &quot;True&quot;
    type: Ready
  lastObservedAt: &quot;2026-09-11T16:11:39Z&quot;
  lastObservedGeneration: 5
  primaryCluster: pscluster1
  primaryClusterEndpoint: ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306Also, we can manually access any running MySQL Pod and confirm the ClusterSet status.shell&amp;gt; kubectl exec -it ps-cluster1-mysql-0 -n ps -- shsh-5.1$ mysqlsh --uri=root@localhost:3306MySQL  localhost:3306 ssl  JS &amp;gt; var clusterset =  dba.getClusterSet()
 MySQL  localhost:3306 ssl  JS &amp;gt; clusterset.status({extended:1})
{
    &quot;clusters&quot;: {
        &quot;pscluster1&quot;: {
            &quot;clusterRole&quot;: &quot;PRIMARY&quot;, 
            &quot;globalStatus&quot;: &quot;OK&quot;, 
            &quot;primary&quot;: &quot;ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306&quot;, 
            &quot;status&quot;: &quot;OK&quot;, 
            &quot;statusText&quot;: &quot;Cluster is ONLINE and can tolerate up to ONE failure.&quot;, 
            &quot;topology&quot;: {
                &quot;ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306&quot;: {
                    &quot;address&quot;: &quot;ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306&quot;, 
                    &quot;memberRole&quot;: &quot;PRIMARY&quot;, 
                    &quot;mode&quot;: &quot;R/W&quot;, 
                    &quot;readReplicas&quot;: {}, 
                    &quot;role&quot;: &quot;HA&quot;, 
                    &quot;status&quot;: &quot;ONLINE&quot;, 
                    &quot;version&quot;: &quot;8.4.10&quot;
                }, 
                &quot;ps-cluster1-mysql-1.ps-cluster1-mysql.ps:3306&quot;: {
                    &quot;address&quot;: &quot;ps-cluster1-mysql-1.ps-cluster1-mysql.ps:3306&quot;, 
                    &quot;memberRole&quot;: &quot;SECONDARY&quot;, 
                    &quot;mode&quot;: &quot;R/O&quot;, 
                    &quot;readReplicas&quot;: {}, 
                    &quot;replicationLagFromImmediateSource&quot;: &quot;&quot;, 
                    &quot;replicationLagFromOriginalSource&quot;: &quot;&quot;, 
                    &quot;role&quot;: &quot;HA&quot;, 
                    &quot;status&quot;: &quot;ONLINE&quot;, 
                    &quot;version&quot;: &quot;8.4.10&quot;
                }, 
                &quot;ps-cluster1-mysql-2.ps-cluster1-mysql.ps:3306&quot;: {
                    &quot;address&quot;: &quot;ps-cluster1-mysql-2.ps-cluster1-mysql.ps:3306&quot;, 
                    &quot;memberRole&quot;: &quot;SECONDARY&quot;, 
                    &quot;mode&quot;: &quot;R/O&quot;, 
                    &quot;readReplicas&quot;: {}, 
                    &quot;replicationLagFromImmediateSource&quot;: &quot;&quot;, 
                    &quot;replicationLagFromOriginalSource&quot;: &quot;&quot;, 
                    &quot;role&quot;: &quot;HA&quot;, 
                    &quot;status&quot;: &quot;ONLINE&quot;, 
                    &quot;version&quot;: &quot;8.4.10&quot;
                }
            }, 
            &quot;transactionSet&quot;: &quot;50cc4081-ad9a-11f1-b815-1602eb3926cb:1-4,65edbac6-ad9a-11f1-9dbe-1602eb3926cb:1-139&quot;
        }, 
        &quot;pscluster2&quot;: {
            &quot;clusterRole&quot;: &quot;REPLICA&quot;, 
            &quot;clusterSetReplication&quot;: {
                &quot;applierStatus&quot;: &quot;APPLIED_ALL&quot;, 
                &quot;applierThreadState&quot;: &quot;Waiting for an event from Coordinator&quot;, 
                &quot;applierWorkerThreads&quot;: 4, 
                &quot;receiver&quot;: &quot;ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr:3306&quot;, 
                &quot;receiverStatus&quot;: &quot;ON&quot;, 
                &quot;receiverThreadState&quot;: &quot;Waiting for source to send event&quot;, 
                &quot;replicationSsl&quot;: &quot;TLS_AES_128_GCM_SHA256 TLSv1.3&quot;, 
                &quot;replicationSslMode&quot;: &quot;REQUIRED&quot;, 
                &quot;source&quot;: &quot;ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306&quot;
            }, 
            &quot;clusterSetReplicationStatus&quot;: &quot;OK&quot;, 
            &quot;globalStatus&quot;: &quot;OK&quot;, 
            &quot;status&quot;: &quot;OK&quot;, 
            &quot;statusText&quot;: &quot;Cluster is ONLINE and can tolerate up to ONE failure.&quot;, 
            &quot;topology&quot;: {
                &quot;ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr:3306&quot;: {
                    &quot;address&quot;: &quot;ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr:3306&quot;, 
                    &quot;memberRole&quot;: &quot;PRIMARY&quot;, 
                    &quot;mode&quot;: &quot;R/O&quot;, 
                    &quot;readReplicas&quot;: {}, 
                    &quot;replicationLagFromImmediateSource&quot;: &quot;&quot;, 
                    &quot;replicationLagFromOriginalSource&quot;: &quot;&quot;, 
                    &quot;role&quot;: &quot;HA&quot;, 
                    &quot;status&quot;: &quot;ONLINE&quot;, 
                    &quot;version&quot;: &quot;8.4.10&quot;
                }, 
                &quot;ps-cluster2-mysql-1.ps-cluster2-mysql.ps-dr:3306&quot;: {
                    &quot;address&quot;: &quot;ps-cluster2-mysql-1.ps-cluster2-mysql.ps-dr:3306&quot;, 
                    &quot;memberRole&quot;: &quot;SECONDARY&quot;, 
                    &quot;mode&quot;: &quot;R/O&quot;, 
                    &quot;readReplicas&quot;: {}, 
                    &quot;replicationLagFromImmediateSource&quot;: &quot;&quot;, 
                    &quot;replicationLagFromOriginalSource&quot;: &quot;&quot;, 
                    &quot;role&quot;: &quot;HA&quot;, 
                    &quot;status&quot;: &quot;ONLINE&quot;, 
                    &quot;version&quot;: &quot;8.4.10&quot;
                }, 
                &quot;ps-cluster2-mysql-2.ps-cluster2-mysql.ps-dr:3306&quot;: {
                    &quot;address&quot;: &quot;ps-cluster2-mysql-2.ps-cluster2-mysql.ps-dr:3306&quot;, 
                    &quot;memberRole&quot;: &quot;SECONDARY&quot;, 
                    &quot;mode&quot;: &quot;R/O&quot;, 
                    &quot;readReplicas&quot;: {}, 
                    &quot;replicationLagFromImmediateSource&quot;: &quot;&quot;, 
                    &quot;replicationLagFromOriginalSource&quot;: &quot;&quot;, 
                    &quot;role&quot;: &quot;HA&quot;, 
                    &quot;status&quot;: &quot;ONLINE&quot;, 
                    &quot;version&quot;: &quot;8.4.10&quot;
                }
            }, 
            &quot;transactionSet&quot;: &quot;50cc4081-ad9a-11f1-b815-1602eb3926cb:1-4,65edbac6-ad9a-11f1-9dbe-1602eb3926cb:1-139&quot;, 
            &quot;transactionSetConsistencyStatus&quot;: &quot;OK&quot;, 
            &quot;transactionSetErrantGtidSet&quot;: &quot;&quot;, 
            &quot;transactionSetMissingGtidSet&quot;: &quot;&quot;
        }
    }, 
    &quot;domainName&quot;: &quot;my-cluster-set&quot;, 
    &quot;globalPrimaryInstance&quot;: &quot;ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306&quot;, 
    &quot;metadataServer&quot;: &quot;ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306&quot;, 
    &quot;primaryCluster&quot;: &quot;pscluster1&quot;, 
    &quot;status&quot;: &quot;HEALTHY&quot;, 
    &quot;statusText&quot;: &quot;All Clusters available.&quot;
}
Validate Replication 

Log in to the Primary member and perform some writes.

shell&amp;gt; export PRIMARY=$(kubectl get pods -n ps -l mysql.percona.com/primary=true -o jsonpath='{.items[0].metadata.name}')
shell&amp;gt; echo $PRIMARY;
ps-cluster1-mysql-0

shell&amp;gt; kubectl exec -it ps-cluster1-mysql-0 -n ps -- sh
shell&amp;gt; mysql -uroot -pmysql&amp;gt; CREATE DATABASE IF NOT EXISTS test;
mysql&amp;gt; CREATE TABLE IF NOT EXISTS test.t1 (id INT PRIMARY KEY); INSERT INTO test.t1 VALUES (1);

Connect to the DR and verify the sync.

shell&amp;gt; kubectl exec -it ps-cluster2-mysql-0 -n ps-dr -- sh
shell&amp;gt; mysql -uroot -pmysql&amp;gt; SELECT * FROM test.t1;
+----+
| id |
+----+
|  1 |
+----+We can also visit any MySQL Pod and run the following command to get the Primary member and group replication details.mysql&amp;gt; select * from performance_schema.replication_group_members;
+---------------------------+--------------------------------------+------------------------------------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST                              | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+------------------------------------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 50cc4081-ad9a-11f1-b815-1602eb3926cb | ps-cluster1-mysql-0.ps-cluster1-mysql.ps |        3306 | ONLINE       | PRIMARY     | 8.4.10         | MySQL                      |
| group_replication_applier | 87137873-ad9a-11f1-ae3f-36b4b899b0ff | ps-cluster1-mysql-1.ps-cluster1-mysql.ps |        3306 | ONLINE       | SECONDARY   | 8.4.10         | MySQL                      |
| group_replication_applier | b952f0d3-ad9a-11f1-95a5-d6e7036e522a | ps-cluster1-mysql-2.ps-cluster1-mysql.ps |        3306 | ONLINE       | SECONDARY   | 8.4.10         | MySQL                      |
+---------------------------+--------------------------------------+------------------------------------------+-------------+--------------+-------------+----------------+----------------------------+
3 rows in set (0.00 sec)
DC-DR switchover/failover
Performing a planned switchover or an ad hoc failover process is quite simple here. All we need to execute the operations below.
Switchover:
shell&amp;gt; kubectl patch ps-clusterset my-cluster-set -n $SOURCE_NS \
  --type=merge -p '{&quot;spec&quot;:{&quot;primaryCluster&quot;:&quot;replicacluster&quot;}}'Forced Failover:shell&amp;gt; kubectl patch ps-clusterset my-cluster-set -n $SOURCE_NS --type=merge -p '{
  &quot;spec&quot;: {
    &quot;primaryCluster&quot;: &quot;replicacluster&quot;,
    &quot;unsafeFlags&quot;: {
      &quot;forcedFailover&quot;: true
    }
  }
}'So let’s try a Primary switchover activity from DC pscluster1 to DR pscluster2. Currently, pscluster2 has a REPLICA role.MySQL  localhost:3306 ssl  JS &amp;gt; clusterset.status()
{
    &quot;clusters&quot;: {
        &quot;pscluster1&quot;: {
            &quot;clusterRole&quot;: &quot;PRIMARY&quot;, 
            &quot;globalStatus&quot;: &quot;OK&quot;, 
            &quot;primary&quot;: &quot;ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306&quot;
        }, 
        &quot;pscluster2&quot;: {
            &quot;clusterRole&quot;: &quot;REPLICA&quot;, 
            &quot;clusterSetReplicationStatus&quot;: &quot;OK&quot;, 
            &quot;globalStatus&quot;: &quot;OK&quot;
        }
    }, 
    &quot;domainName&quot;: &quot;my-cluster-set&quot;, 
    &quot;globalPrimaryInstance&quot;: &quot;ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306&quot;, 
    &quot;primaryCluster&quot;: &quot;pscluster1&quot;, 
    &quot;status&quot;: &quot;HEALTHY&quot;, 
    &quot;statusText&quot;: &quot;All Clusters available.&quot;
}
Run Switchover command:
shell&amp;gt; kubectl patch ps-clusterset my-cluster-set -n ps \
  --type=merge -p '{&quot;spec&quot;:{&quot;primaryCluster&quot;:&quot;pscluster2&quot;}}'

perconaservermysqlclusterset.ps.percona.com/my-cluster-set patched
Watch the progress:
shell&amp;gt; kubectl get ps-clusterset my-cluster-set -n ps -w
shell&amp;gt; kubectl get jobs -n ps | grep switchoverAfter some time, we can see pscluster2 become the Primary cluster.shell&amp;gt; kubectl get ps-clusterset my-cluster-set -n ps -w
NAME             PRIMARY      ENDPOINT                                           NAME             PRIMARY      ENDPOINT   READY
my-cluster-set   pscluster1              False
my-cluster-set   pscluster2   ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr:3306   Truekubectl exec -it ps-cluster1-mysql-0 -n ps -- sh
sh-5.1$ mysqlsh --uri=root@localhost:3306MySQL  localhost:3306 ssl  JS &amp;gt; clusterset.status()
{
    &quot;clusters&quot;: {
        &quot;pscluster1&quot;: {
            &quot;clusterRole&quot;: &quot;REPLICA&quot;, 
            &quot;clusterSetReplicationStatus&quot;: &quot;OK&quot;, 
            &quot;globalStatus&quot;: &quot;OK&quot;
        }, 
        &quot;pscluster2&quot;: {
            &quot;clusterRole&quot;: &quot;PRIMARY&quot;, 
            &quot;globalStatus&quot;: &quot;OK&quot;, 
            &quot;primary&quot;: &quot;ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr:3306&quot;
        }
    }, 
    &quot;domainName&quot;: &quot;my-cluster-set&quot;, 
    &quot;globalPrimaryInstance&quot;: &quot;ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr:3306&quot;, 
    &quot;primaryCluster&quot;: &quot;pscluster2&quot;, 
    &quot;status&quot;: &quot;HEALTHY&quot;, 
    &quot;statusText&quot;: &quot;All Clusters available.&quot;
}Once the switchover finishes successfully, all new writes now go to the new Primary cluster ps-cluster2 and the old DC “ps-cluster1” will automatically become the Async Replica. The application should connect with a load balancer (Haproxy/MySQL Router) endpoint, e.g., (ps-cluster2-haproxy), which will forward all requests to the backend node (by default) to the Primary member.shell&amp;gt; kubectl get svc ps-cluster2-haproxy -n ps-dr
NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                          AGE
ps-cluster2-haproxy   ClusterIP   34.118.226.22   &amp;lt;none&amp;gt;        3306/TCP,3307/TCP,3309/TCP,33060/TCP,33062/TCP   12h
Key Takeaways
Configuring and managing a multi-region topology manually or on virtual machines can be quite challenging. By packaging these components within the Percona PS Operator/K8S, setting up cross-region environments becomes far simpler. This streamlined deployment is critical for establishing robust disaster recovery solutions, offloading production workloads, or scaling read operations. Furthermore, built-in support for seamless DC-DR switchovers and ad hoc failovers adds significant value to the architecture.
Before deploying these topologies into production environments, it is strongly advised to thoroughly test and validate their behaviour in lower or non-production environments. Proceed with production deployment only after achieving full confidence in the setup. 

The post Group Replication Beyond a Single Cluster: DC-DR with Percona (PS MySQL) Operator appeared first on Percona.</description>
    <content:encoded><![CDATA[<p>A while ago, we discussed the cross-site replication feature of the <a href="https://www.percona.com/blog/deploying-cross-site-replication-in-percona-operator-for-mysql-pxc/">Percona PXC operator</a>. Recently, a similar cross-site replication feature was introduced in the Percona (PS MySQL) operator <a href="https://docs.percona.com/percona-operator-for-mysql/1.2.0/ReleaseNotes/Kubernetes-Operator-for-PS-RN1.2.0.html#cross-site-replication-for-group-replication-clusters">v1.2.0</a>, a topology based on <b>Group Replication/InnoDB Cluster.</b></p>
<p><span>In this blog post, we will explore how to add a </span><strong>DR Cluster</strong><span> to an existing </span><b>DC Cluster </b><span>to form a </span><b>ClusterSet</b><span> environment, which provides a seamless switchover between DC/DR members. The good part is that all the complexity and configuration will be managed by the Percona operator, with very few setup steps required.</span></p>
<figure aria-describedby="caption-attachment-53590" class="wp-caption alignnone"><img fetchpriority="high" decoding="async" class="size-full wp-image-53590" src="https://www.percona.com/wp-content/uploads/2026/09/image1.png" alt="Group Replication (DC-DR) using Innodb ClusterSet" width="960" height="540" srcset="https://www.percona.com/wp-content/uploads/2026/09/image1.png 960w, https://www.percona.com/wp-content/uploads/2026/09/image1-300x169.png 300w, https://www.percona.com/wp-content/uploads/2026/09/image1-768x432.png 768w" sizes="(max-width: 960px) 100vw, 960px"><figcaption class="wp-caption-text">Group Replication (DC-DR) using Innodb ClusterSet</figcaption></figure>
<p> </p>
<p><span>Let’s discuss how we can implement a cross-site replication (DC-DR) topology across two separate database clusters.</span></p>
<h3><span>Environment used for Demonstration</span></h3>
<ul>
<li aria-level="1"><span>Two separate database clusters (</span><b>pscluster1</b><span> and </span><b>pscluster2</b><span>) are deployed within a single GCP/Kubernetes environment, each managed by a dedicated operator.</span></li>
<li aria-level="1"><span>Percona Operator for MySQL( based on Percona Server for MySQL) has a version. 1.2.0</span></li>
<li aria-level="1"><span>MySQL version 8.4.10.</span></li>
<li aria-level="1"><span>Network connectivity between DC/DR components.</span></li>
</ul>
<h3><span>DC (</span><b>pscluster1</b><span>) configuration</span></h3>
<p><strong>Step 1: </strong> <a href="https://docs.percona.com/percona-operator-for-mysql/1.2.0/gke.html">Deploying the cluster</a> on the DC side.</p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get ps -n ps
NAME          REPLICATION         ENDPOINT                 STATE   MYSQL   ORCHESTRATOR   HAPROXY   ROUTER   AGE
ps-cluster1   group-replication   ps-cluster1-haproxy.ps   ready   3                      3                  28m</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get pods -n ps
NAME                                             READY   STATUS    RESTARTS   AGE
percona-server-mysql-operator-6b78d5f68c-bzg6r   1/1     Running   0          30m
ps-cluster1-haproxy-0                            2/2     Running   0          28m
ps-cluster1-haproxy-1                            2/2     Running   0          27m
ps-cluster1-haproxy-2                            2/2     Running   0          27m
ps-cluster1-mysql-0                              2/2     Running   0          29m
ps-cluster1-mysql-1                              2/2     Running   0          28m
ps-cluster1-mysql-2                              2/2     Running   0          26m</pre><p><strong>Step 2:</strong> Retrieve the DC’s InnoDB cluster name, which will be required later to construct the ClusterSet.</p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get ps ps-cluster1 -n ps -o jsonpath='{.status.innodbClusterName}{"\n"}'
pscluster1</pre><p><strong>Step 3:</strong><span> Get a service endpoint which is used when setting up the ClusterSet environment. We will use </span><strong>ps-cluster1-mysql-primary</strong>,<span> which is mapped to the current Primary node in the existing cluster.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get services -n ps

NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                           AGE
ps-cluster1-haproxy         ClusterIP   34.118.225.215   &lt;none&gt;        3306/TCP,3307/TCP,3309/TCP,33060/TCP,33062/TCP    29m
ps-cluster1-mysql           ClusterIP   None             &lt;none&gt;        3306/TCP,33062/TCP,33060/TCP,6450/TCP,33061/TCP   29m
ps-cluster1-mysql-primary   ClusterIP   34.118.229.137   &lt;none&gt;        3306/TCP,33062/TCP,33060/TCP,6450/TCP,33061/TCP   29m
ps-cluster1-mysql-proxy     ClusterIP   None             &lt;none&gt;        3306/TCP,33062/TCP,33060/TCP,6450/TCP,33061/TCP   29m
ps-cluster1-mysql-unready   ClusterIP   None             &lt;none&gt;        3306/TCP,33062/TCP,33060/TCP,6450/TCP,33061/TCP   29m</pre><p><b>Step 4: </b><span>The DR site should have the same credentials as DC. We need to export the DC secret containing the credentials and import it into the DR cluster.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get secret ps-cluster1-secrets -n ps -o yaml &gt; source-secret.yaml</pre><p><span>We also need to perform a couple of cleanups in the exported secret file.</span></p>
<ul>
<li aria-level="1"><span>Remove the annotations, creationTimestamp, resourceVersion, selfLink, and uid metadata fields.</span></li>
<li aria-level="1"><span>As per the requirement, change the namespace/instance and other unwanted details as per your replica site.</span></li>
</ul>
<p><span>E.g,</span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; yq eval 'del(.metadata.ownerReferences, .metadata.annotations, .metadata.creationTimestamp, .metadata.resourceVersion, .metadata.selfLink, .metadata.uid)' source-secret.yaml &gt; replica-secret.yaml</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; yq eval '.metadata.namespace = "ps-dr"' -i replica-secret.yaml</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; sed -i '' 's/ps-cluster1/ps-cluster2/g' replica-secret.yaml</pre><p><span>The final ready secret file looks like this. </span></p><pre class="urvanov-syntax-highlighter-plain-tag">apiVersion: v1
data:
  clusterset: XVpMQz0+LEVDe3kyQGR6TQ==
  heartbeat: YmFONW1KTE8oLTxdVVB7Kg==
  monitor: UC1oOHokRksoMnpSMWsmJg==
  operator: SDMrblpSQm0yRHdhV3dteX0=
  orchestrator: cEVqMEUlaGp5fShudyZURHtl
  replication: ZjldZlkwa3ZwNWU8KVpsag==
  root: e3BycEl+LlcrPFVUUTwpaG4=
  xtrabackup: eFhNNENIRF4wJXZ7bjxbKi11ag==
kind: Secret
metadata:
  labels:
    app.kubernetes.io/component: database
    app.kubernetes.io/instance: ps-cluster2
    app.kubernetes.io/managed-by: percona-server-mysql-operator
    app.kubernetes.io/name: mysql
    app.kubernetes.io/part-of: percona-server
  name: ps-cluster2-secrets
  namespace: ps-dr
type: Opaque</pre><p></p>
<h3></h3>
<h3><span>DR (</span><b>pscluster2</b><span>) Cluster configuration</span></h3>
<p><strong>Step 1:</strong><span> First, we need to import the modified secret file </span><strong>replica-secret.yaml</strong> <span>and initialise the DR cluster.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl apply -f deploy/replica-secret.yaml -n ps-dr
secret/ps-cluster2-secrets created</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get secrets -n ps-dr
NAME                      TYPE     DATA   AGE
...
ps-cluster2-secrets   Opaque   8      12s
...</pre><p><span>Also, we need to make some modifications to the custom resource file </span><b>cr.yaml</b><span> to initialise the DR cluster.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">metadata:
  finalizers:
    - percona.com/delete-mysql-pods-in-order
#    - percona.com/delete-ssl
#    - percona.com/delete-mysql-pvc
  name: ps-cluster2

spec:
  crVersion: 1.2.0
  secretsName: ps-cluster2-secrets</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">mysql:
    clusterType: group-replication
    size: 3
    image: percona/percona-server:8.4.10-10.1
    bootstrap:
      mode: manual</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl apply -f deploy/cr.yaml -n ps</pre><p><b>Note</b><span>: We set </span><b>spec.mysql.bootstrap.mode</b><span> to </span><b>manual</b><span> so Pod </span><b>mysql-0 </b><span>does not form a Group Replication group until the ClusterSet adopts it and references the secret file we copied from the DC.</span></p>
<p><span>It is also expected that after applying the Custom Resource file </span><b>cr.yaml,</b><span> the Pod </span><b>mysql-0 </b><span>starts but stays </span><b>NotReady</b><span>. The cluster </span><b>ps-cluster2 </b><span>reports the Initialising state and the </span><b>AwaitingExternalBootstrap</b><span> condition in status. Pods </span><b>mysql-1 </b><span>and </span><b>mysql-2 </b><span>do not start until Pod </span><b>mysql-0</b><span>  joins the Group Replication.</span></p>
<p><span>Once we deploy the custom resource, we can notice the status below. </span><b>Please note that </b><span>the complete Pods will be ready only once DR successfully syncs and joins the DC cluster. </span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get ps -n ps-dr
NAME          REPLICATION         ENDPOINT                    STATE          MYSQL   ORCHESTRATOR   HAPROXY   ROUTER   AGE
ps-cluster2   group-replication   ps-cluster2-haproxy.ps-dr   initializing                                             17m</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get pods -n ps-dr
NAME                                             READY   STATUS    RESTARTS   AGE
percona-server-mysql-operator-6b78d5f68c-4gsm9   1/1     Running   0          28m
ps-cluster2-mysql-0</pre><p><b>Step 2:</b><span> We need to note down the DR InnoDB cluster name, which will later be used by the ClusterSet.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get ps ps-cluster2 -n ps -o jsonpath='{.status.innodbClusterName}{"\n"}'
pscluster2</pre><p><b>Step 3:</b><span> We need to identify the DR cluster endpoint that is reachable from the DC. This endpoint will be used to feed the clone and perform other management operations.</span></p>
<p><span>We will use the local FQDN </span><b>ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr.svc.cluster.local</b><span> of the </span><b>ps-cluster2-mysql-0 </b><span>Pod, which is currently initialised and awaiting the ready state.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get pods -n ps-dr
NAME                                             READY   STATUS    RESTARTS   AGE
percona-server-mysql-operator-6b78d5f68c-4gsm9   1/1     Running   0          48m
ps-cluster2-mysql-0                              1/2     Running   0          37m</pre><p><span>By connecting to the DC Primary Pod, we can verify cross-communication between the DC and the DR.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl exec -it ps-cluster1-mysql-0 -n ps -- sh</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">sh-5.1$ curl -v ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr.svc.cluster.local:3306
*   Trying 10.60.1.7:3306...
* Connected to ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr.svc.cluster.local (10.60.1.7) port 3306 (#0)
&gt; GET / HTTP/1.1
&gt; Host: ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr.svc.cluster.local:3306
&gt; User-Agent: curl/7.76.1
&gt; Accept: */*</pre><p></p>
<h3><span>Data Restoration Part</span></h3>
<p><span>By default, when the DR site initialises and attempts to join the ClusterSet, it receives data from the source/DC via the </span><a href="https://dev.mysql.com/doc/mysql-shell/26.7/en/mysql-innodb-cluster-clone-deployment.html"><span>clone recovery </span></a><span>method.</span></p>
<p><span>The Operator uses </span><b>MySQL Shell</b><span> to create a physical snapshot of the dataset from the Source/DC and transfer it to the DR Replica.</span></p>
<p><span>Alternatively, we can manually perform a </span><a href="https://docs.percona.com/percona-operator-for-mysql/1.2.0/backups-ondemand.html"><span>backup</span></a><span> and </span><a href="https://docs.percona.com/percona-operator-for-mysql/1.2.0/backups-restore-to-new-cluster.html"><span>restore</span></a><span> on the target cluster. This strategy helps when integrating the DR site into the ClusterSet, as pre-populating the data and GTID history allows it to apply only delta changes rather than performing a full initial synchronisation. For large datasets or to prevent excessive load and performance degradation on the donor node, a backup-and-restore approach can be considered.</span></p>
<p><span>Here, we will use the clone recovery method to sync the DR cluster.</span></p>
<p><span>Next, we will deploy the ClusterSet configurations so that DR joins the DC cluster via a data clone and connects via Asynchronous Replication to stay in sync.</span></p>
<p><span>Below is the </span><b>clusterset.yaml </b><span>file where we pass the various information like (<strong>S</strong></span><b>ecrets</b><span>, </span><b>DC/DR Endpoints</b><span>, </span><b>Cluster Name </b><span>etc) , which we fetched in some of the above steps earlier.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">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.ps.svc.cluster.local
        port: 3306
    - innodbClusterName: pscluster2
      endpoints:
      - host: ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr.svc.cluster.local
        port: 3306
  mysqlshellRunner:
    image: percona/percona-server:8.4.10-10.1</pre><p>We will apply the changes <span>only to the <strong>DC node</strong></span>.</p><pre class="urvanov-syntax-highlighter-plain-tag">kubectl apply -f deploy/clusterset.yaml</pre><p><b>Please note: </b><span>Member</span><b> “ps-cluster2-mysql-0” </b><span>on the DR side will connect to the DC via </span><b>asynchronous replication</b><span>. While the local members of the DR will join via the </span><b>Group Replication</b><span> mechanism. </span></p>
<p><span>This action will also start a separate backend job on DC, which creates an associated Pod to perform the ClusterSet activity.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get jobs -n ps
NAME                                    STATUS    COMPLETIONS   DURATION   AGE
my-cluster-set-pscluster2-add-replica   Complete             1/1           23s        53s</pre><p><span>….</span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get pods -n ps
NAME                                             READY   STATUS    RESTARTS   AGE
my-cluster-set-pscluster2-add-replica-wk7xd      1/1     Running             0          3s</pre><p><span>As soon as the process completes successfully, the job and associated Pod will be removed from the list.</span></p>
<p><span>There is one extra pod that can be noticed, and it persists. This provides a utility/client for running MySQL Shell commands against the MySQL ClusterSet.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get pods -n ps
NAME                                             READY   STATUS    RESTARTS   AGE
my-cluster-set-runner-5d68fbcc74-svmpw           1/1     Running   0          10h</pre><p><span>If the job fails or errors occur, we need to investigate the exact problem using the information below.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">kubectl describe job &lt;jobname&gt;
kubectl describe pod &lt;add-replica pod&gt;
kubectl logs &lt;add-replica pod&gt;</pre><p><span>Finally, we can check the Pod status on the DR cluster. It will now reflect all MySQL and haproxy pods in the fully completed/ready state.  </span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get pods -n ps-dr
NAME                                             READY   STATUS    RESTARTS      AGE
percona-server-mysql-operator-6b78d5f68c-4gsm9   1/1     Running   0             11h
ps-cluster2-haproxy-0                            2/2     Running   0             36m
ps-cluster2-haproxy-1                            2/2     Running   0             36m
ps-cluster2-haproxy-2                            2/2     Running   0             36m
ps-cluster2-mysql-0                              2/2     Running   1 (37m ago)   11h
ps-cluster2-mysql-1                              2/2     Running   1 (36m ago)   36m
ps-cluster2-mysql-2                              2/2     Running   1 (34m ago)   35m</pre><p></p>
<h3><span>Verification of ClusterSet completion</span></h3>
<p><span>We can check the </span><span>ClusterSet information below if it has been processed successfully without any errors.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get ps-clusterset my-cluster-set -n ps
NAME             PRIMARY      ENDPOINT                                        READY
my-cluster-set   pscluster1   ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306   True</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get ps-clusterset my-cluster-set -n ps -o yaml
apiVersion: ps.percona.com/v1
kind: PerconaServerMySQLClusterSet
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"ps.percona.com/v1","kind":"PerconaServerMySQLClusterSet","metadata":{"annotations":{},"finalizers":["percona.com/clusterset-dissolve"],"name":"my-cluster-set","namespace":"ps"},"spec":{"clusters":[{"endpoints":[{"host":"ps-cluster1-mysql-primary.ps.svc.cluster.local","port":3306}],"innodbClusterName":"pscluster1"},{"endpoints":[{"host":"ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr.svc.cluster.local","port":3306}],"innodbClusterName":"pscluster2"}],"createReplicaClusterOptions":{"recoveryMethod":"clone"},"credentialsSecret":{"key":"clusterset","name":"ps-cluster1-secrets"},"mysqlshellRunner":{"image":"percona/percona-server:8.4.10-10.1"},"primaryCluster":"pscluster1","sslMode":"AUTO"}}
  creationTimestamp: "2026-09-11T05:20:08Z"
  finalizers:
  - percona.com/clusterset-dissolve
  generation: 5
  name: my-cluster-set
  namespace: ps
  resourceVersion: "1789143099049519007"
  uid: 6cfdce40-e1e0-47c8-bdae-1cd847938f29
spec:
  clusters:
  - endpoints:
    - host: ps-cluster1-mysql-primary.ps.svc.cluster.local
      port: 3306
    innodbClusterName: pscluster1
  - endpoints:
    - host: ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr.svc.cluster.local
      port: 3306
    innodbClusterName: pscluster2
  createReplicaClusterOptions:
    recoveryMethod: clone
  credentialsSecret:
    key: clusterset
    name: ps-cluster1-secrets
  mysqlshellRunner:
    image: percona/percona-server:8.4.10-10.1
  primaryCluster: pscluster1
  sslMode: AUTO
status:
  clusters:
    pscluster1:
      clusterRole: PRIMARY
      globalStatus: OK
      primary: ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306
    pscluster2:
      clusterRole: REPLICA
      globalStatus: OK
      primary: ""
  conditions:
  - lastTransitionTime: "2026-09-11T05:25:07Z"
    message: ""
    reason: DeploymentReady
    status: "True"
    type: MySQLShellRunnerReady
  - lastTransitionTime: "2026-09-11T05:25:10Z"
    message: ""
    reason: ClusterSetBootstrapped
    status: "True"
    type: ClusterSetBootstrapped
  - lastTransitionTime: "2026-09-11T05:25:13Z"
    message: All Clusters available.
    reason: ClusterSetHealthy
    status: "True"
    type: Ready
  lastObservedAt: "2026-09-11T16:11:39Z"
  lastObservedGeneration: 5
  primaryCluster: pscluster1
  primaryClusterEndpoint: ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306</pre><p><span>Also, we can manually access any running MySQL Pod and confirm the ClusterSet status.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl exec -it ps-cluster1-mysql-0 -n ps -- sh</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">sh-5.1$ mysqlsh --uri=root@localhost:3306</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">MySQL  localhost:3306 ssl  JS &gt; var clusterset =  dba.getClusterSet()
 MySQL  localhost:3306 ssl  JS &gt; clusterset.status({extended:1})
{
    "clusters": {
        "pscluster1": {
            "clusterRole": "PRIMARY", 
            "globalStatus": "OK", 
            "primary": "ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306", 
            "status": "OK", 
            "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.", 
            "topology": {
                "ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306": {
                    "address": "ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306", 
                    "memberRole": "PRIMARY", 
                    "mode": "R/W", 
                    "readReplicas": {}, 
                    "role": "HA", 
                    "status": "ONLINE", 
                    "version": "8.4.10"
                }, 
                "ps-cluster1-mysql-1.ps-cluster1-mysql.ps:3306": {
                    "address": "ps-cluster1-mysql-1.ps-cluster1-mysql.ps:3306", 
                    "memberRole": "SECONDARY", 
                    "mode": "R/O", 
                    "readReplicas": {}, 
                    "replicationLagFromImmediateSource": "", 
                    "replicationLagFromOriginalSource": "", 
                    "role": "HA", 
                    "status": "ONLINE", 
                    "version": "8.4.10"
                }, 
                "ps-cluster1-mysql-2.ps-cluster1-mysql.ps:3306": {
                    "address": "ps-cluster1-mysql-2.ps-cluster1-mysql.ps:3306", 
                    "memberRole": "SECONDARY", 
                    "mode": "R/O", 
                    "readReplicas": {}, 
                    "replicationLagFromImmediateSource": "", 
                    "replicationLagFromOriginalSource": "", 
                    "role": "HA", 
                    "status": "ONLINE", 
                    "version": "8.4.10"
                }
            }, 
            "transactionSet": "50cc4081-ad9a-11f1-b815-1602eb3926cb:1-4,65edbac6-ad9a-11f1-9dbe-1602eb3926cb:1-139"
        }, 
        "pscluster2": {
            "clusterRole": "REPLICA", 
            "clusterSetReplication": {
                "applierStatus": "APPLIED_ALL", 
                "applierThreadState": "Waiting for an event from Coordinator", 
                "applierWorkerThreads": 4, 
                "receiver": "ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr:3306", 
                "receiverStatus": "ON", 
                "receiverThreadState": "Waiting for source to send event", 
                "replicationSsl": "TLS_AES_128_GCM_SHA256 TLSv1.3", 
                "replicationSslMode": "REQUIRED", 
                "source": "ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306"
            }, 
            "clusterSetReplicationStatus": "OK", 
            "globalStatus": "OK", 
            "status": "OK", 
            "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.", 
            "topology": {
                "ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr:3306": {
                    "address": "ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr:3306", 
                    "memberRole": "PRIMARY", 
                    "mode": "R/O", 
                    "readReplicas": {}, 
                    "replicationLagFromImmediateSource": "", 
                    "replicationLagFromOriginalSource": "", 
                    "role": "HA", 
                    "status": "ONLINE", 
                    "version": "8.4.10"
                }, 
                "ps-cluster2-mysql-1.ps-cluster2-mysql.ps-dr:3306": {
                    "address": "ps-cluster2-mysql-1.ps-cluster2-mysql.ps-dr:3306", 
                    "memberRole": "SECONDARY", 
                    "mode": "R/O", 
                    "readReplicas": {}, 
                    "replicationLagFromImmediateSource": "", 
                    "replicationLagFromOriginalSource": "", 
                    "role": "HA", 
                    "status": "ONLINE", 
                    "version": "8.4.10"
                }, 
                "ps-cluster2-mysql-2.ps-cluster2-mysql.ps-dr:3306": {
                    "address": "ps-cluster2-mysql-2.ps-cluster2-mysql.ps-dr:3306", 
                    "memberRole": "SECONDARY", 
                    "mode": "R/O", 
                    "readReplicas": {}, 
                    "replicationLagFromImmediateSource": "", 
                    "replicationLagFromOriginalSource": "", 
                    "role": "HA", 
                    "status": "ONLINE", 
                    "version": "8.4.10"
                }
            }, 
            "transactionSet": "50cc4081-ad9a-11f1-b815-1602eb3926cb:1-4,65edbac6-ad9a-11f1-9dbe-1602eb3926cb:1-139", 
            "transactionSetConsistencyStatus": "OK", 
            "transactionSetErrantGtidSet": "", 
            "transactionSetMissingGtidSet": ""
        }
    }, 
    "domainName": "my-cluster-set", 
    "globalPrimaryInstance": "ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306", 
    "metadataServer": "ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306", 
    "primaryCluster": "pscluster1", 
    "status": "HEALTHY", 
    "statusText": "All Clusters available."
}</pre><p></p>
<h3><span>Validate Replication </span></h3>
<ul>
<li aria-level="1"><span>Log in to the Primary member and perform some writes.</span></li>
</ul>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; export PRIMARY=$(kubectl get pods -n ps -l mysql.percona.com/primary=true -o jsonpath='{.items[0].metadata.name}')
shell&gt; echo $PRIMARY;
ps-cluster1-mysql-0

shell&gt; kubectl exec -it ps-cluster1-mysql-0 -n ps -- sh
shell&gt; mysql -uroot -p</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; CREATE DATABASE IF NOT EXISTS test;
mysql&gt; CREATE TABLE IF NOT EXISTS test.t1 (id INT PRIMARY KEY); INSERT INTO test.t1 VALUES (1);</pre><p></p>
<ul>
<li aria-level="1"><span>Connect to the DR and verify the sync.</span></li>
</ul>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl exec -it ps-cluster2-mysql-0 -n ps-dr -- sh
shell&gt; mysql -uroot -p</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; SELECT * FROM test.t1;
+----+
| id |
+----+
|  1 |
+----+</pre><p><span>We can also visit any MySQL Pod and run the following command to get the Primary member and group replication details.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; select * from performance_schema.replication_group_members;
+---------------------------+--------------------------------------+------------------------------------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST                              | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+------------------------------------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 50cc4081-ad9a-11f1-b815-1602eb3926cb | ps-cluster1-mysql-0.ps-cluster1-mysql.ps |        3306 | ONLINE       | PRIMARY     | 8.4.10         | MySQL                      |
| group_replication_applier | 87137873-ad9a-11f1-ae3f-36b4b899b0ff | ps-cluster1-mysql-1.ps-cluster1-mysql.ps |        3306 | ONLINE       | SECONDARY   | 8.4.10         | MySQL                      |
| group_replication_applier | b952f0d3-ad9a-11f1-95a5-d6e7036e522a | ps-cluster1-mysql-2.ps-cluster1-mysql.ps |        3306 | ONLINE       | SECONDARY   | 8.4.10         | MySQL                      |
+---------------------------+--------------------------------------+------------------------------------------+-------------+--------------+-------------+----------------+----------------------------+
3 rows in set (0.00 sec)</pre><p></p>
<h3><span>DC-DR switchover/failover</span></h3>
<p><span>Performing a planned switchover or an ad hoc failover process is quite simple here. All we need to execute the operations below.</span></p>
<p><b>Switchover:</b><b><br>
</b></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl patch ps-clusterset my-cluster-set -n $SOURCE_NS \
  --type=merge -p '{"spec":{"primaryCluster":"replicacluster"}}'</pre><p><b>Forced Failover:</b></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl patch ps-clusterset my-cluster-set -n $SOURCE_NS --type=merge -p '{
  "spec": {
    "primaryCluster": "replicacluster",
    "unsafeFlags": {
      "forcedFailover": true
    }
  }
}'</pre><p><span>So let’s try a Primary switchover activity from DC </span><b>pscluster1</b><span> to DR </span><b>pscluster2. </b><span>Currently</span><b>, </b><span>pscluster2 has a </span><b>REPLICA</b><span> role.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">MySQL  localhost:3306 ssl  JS &gt; clusterset.status()
{
    "clusters": {
        "pscluster1": {
            "clusterRole": "PRIMARY", 
            "globalStatus": "OK", 
            "primary": "ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306"
        }, 
        "pscluster2": {
            "clusterRole": "REPLICA", 
            "clusterSetReplicationStatus": "OK", 
            "globalStatus": "OK"
        }
    }, 
    "domainName": "my-cluster-set", 
    "globalPrimaryInstance": "ps-cluster1-mysql-0.ps-cluster1-mysql.ps:3306", 
    "primaryCluster": "pscluster1", 
    "status": "HEALTHY", 
    "statusText": "All Clusters available."
}</pre><p></p>
<h4><span>Run Switchover command:</span></h4>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl patch ps-clusterset my-cluster-set -n ps \
  --type=merge -p '{"spec":{"primaryCluster":"pscluster2"}}'

perconaservermysqlclusterset.ps.percona.com/my-cluster-set patched</pre><p></p>
<h4><span>Watch the progress:</span></h4>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get ps-clusterset my-cluster-set -n ps -w
shell&gt; kubectl get jobs -n ps | grep switchover</pre><p><span>After some time, we can see </span><b>pscluster2</b><span> become the </span><b>Primary</b><span> cluster.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get ps-clusterset my-cluster-set -n ps -w
NAME             PRIMARY      ENDPOINT                                           NAME             PRIMARY      ENDPOINT   READY
my-cluster-set   pscluster1              False
my-cluster-set   pscluster2   ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr:3306   True</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">kubectl exec -it ps-cluster1-mysql-0 -n ps -- sh
sh-5.1$ mysqlsh --uri=root@localhost:3306</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">MySQL  localhost:3306 ssl  JS &gt; clusterset.status()
{
    "clusters": {
        "pscluster1": {
            "clusterRole": "REPLICA", 
            "clusterSetReplicationStatus": "OK", 
            "globalStatus": "OK"
        }, 
        "pscluster2": {
            "clusterRole": "PRIMARY", 
            "globalStatus": "OK", 
            "primary": "ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr:3306"
        }
    }, 
    "domainName": "my-cluster-set", 
    "globalPrimaryInstance": "ps-cluster2-mysql-0.ps-cluster2-mysql.ps-dr:3306", 
    "primaryCluster": "pscluster2", 
    "status": "HEALTHY", 
    "statusText": "All Clusters available."
}</pre><p><span>Once the switchover finishes successfully, all new writes now go to the new Primary cluster </span><b>ps-cluster2 </b><span>and the old DC </span><b>“ps-cluster1</b><span>” will automatically become the Async Replica. The application should connect with</span><b> a load balancer (Haproxy/MySQL Router) </b><span>endpoint, e.g., (</span><b>ps-cluster2-haproxy</b><span>), which will forward all requests to the backend node (by default) to the Primary member.</span></p><pre class="urvanov-syntax-highlighter-plain-tag">shell&gt; kubectl get svc ps-cluster2-haproxy -n ps-dr
NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                          AGE
ps-cluster2-haproxy   ClusterIP   34.118.226.22   &lt;none&gt;        3306/TCP,3307/TCP,3309/TCP,33060/TCP,33062/TCP   12h</pre><p></p>
<h2><span>Key Takeaways</span></h2>
<p><span>Configuring and managing a</span><a href="https://docs.percona.com/percona-operator-for-mysql/1.2.0/replication.html"><span> multi-region topology </span></a><span>manually or on virtual machines can be quite challenging. By packaging these components within the </span><b>Percona PS Operator/K8S</b><span>, setting up cross-region environments becomes far simpler. This streamlined deployment is critical for establishing robust disaster recovery solutions, offloading production workloads, or scaling read operations. Furthermore, built-in support for seamless DC-DR switchovers and ad hoc failovers adds significant value to the architecture.</span></p>
<p><b>Before deploying these topologies into production environments</b><span>, it is strongly advised to thoroughly test and validate their behaviour in lower or non-production environments. Proceed with production deployment only after achieving full confidence in the setup. </span></p>

<p>The post <a href="https://www.percona.com/blog/group-replication-beyond-a-single-cluster-dc-dr-with-percona-ps-mysql-operator/">Group Replication Beyond a Single Cluster: DC-DR with Percona (PS MySQL) Operator</a> appeared first on <a href="https://www.percona.com/">Percona</a>.</p>]]></content:encoded>
    <pubDate>Thu, 17 Sep 2026 06:43:32 +0000</pubDate>
    <dc:creator>MySQL Performance Blog</dc:creator>
    <category>Cloud</category>
    <category>Database Trends</category>
    <category>Featured</category>
    <category>Insight for DBAs</category>
    <category>Kubernetes</category>
    <category>MySQL</category>
    <category>Open Source</category>
    <category>Operator</category>
    <category>Percona Software</category>
    <category>Uncategorized</category>
    <category>Innodb ClusterSet</category>
    <category>kubernetes operators</category>
    <category>MySQL Group Replication</category>
    <category>MySQL High Availability</category>
    <category>MySQL Replication</category>
    <category>Percona Kubernetes Operators</category>
    <category>Perc</category>
  </item>

  <item>
    <title>Too many GCache Page Files in MySQL Data Directory</title>
    <guid isPermaLink="false">https://www.percona.com/?p=51554</guid>
    <link>https://www.percona.com/blog/too-many-gcache-page-files-in-mysql-data-directory/</link>
    <description>A few thousand gcache.page.* files in a Percona XtraDB Cluster (PXC) data directory is not something you see every day. We came across a case where these files had been accumulating over time and slowly consuming disk space. So, let’s dig into what happened.
At first glance, it looked like GCache had simply stopped cleaning itself up. The investigation started by answering two simple questions: when did the files start appearing, and what changed in the cluster at that time?
Finding the starting point
The oldest files showed the issue started on July 9.[hostx] percona@hostx: ~ $ ls -lh /var/lib/mysql/mysql-data/gcache.page.*
-rw-r----- 1 mysql mysql 128M Jul 9 21:42 /var/lib/mysql/mysql-data/gcache.page.000000
-rw-r----- 1 mysql mysql 128M Jul 9 21:42 /var/lib/mysql/mysql-data/gcache.page.000001
-rw-r----- 1 mysql mysql 128M Jul 9 21:42 /var/lib/mysql/mysql-data/gcache.page.000002
...
-rw-r----- 1 mysql mysql 128M Jul 26 07:45 /var/lib/mysql/mysql-data/gcache.page.005976
-rw-r----- 1 mysql mysql 128M Jul 26 07:50 /var/lib/mysql/mysql-data/gcache.page.005977
-rw-r----- 1 mysql mysql 128M Jul 26 07:56 /var/lib/mysql/mysql-data/gcache.page.005978The newest files showed they stopped being created on July 26, which immediately provided a timeline to investigate.
The creation of GCache page files wasn’t random. It started at a specific point in time and stopped after the next MySQL restart.
Can large transactions cause this?
Normally, you don’t see thousands of GCache page files unless Galera cannot reclaim old pages or an exceptionally large writeset forces additional page allocation. In this environment, the GCache ring file was around 60 GB, making the large writeset theory very unlikely – actually Impossible! Because there’s a hard limit of the largest transaction size at 2GB.
That pushed the investigation toward the error log, where Galera was found logging:2026-07-09T21:41:04.617132Z 91699 [Note] [MY-000000] [Galera] Freezing gcache purge at 16198595494
2026-07-09T21:41:04.622051Z 0 [Note] [MY-000000] [Galera] Created page /var/lib/mysql/mysql-data/gcache.page.000000 of size 134217728 bytesThis was the first strong clue. When gcache.freeze_purge_at_seqno is active, Galera stops reclaiming old GCache pages. As replication continues, new page files are allocated while existing ones remain on disk.
Correlating with cluster activity
Looking a few seconds later in the error log revealed a cluster partition. The timing is difficult to ignore: Galera froze GCache purging, created the first page file, and then recorded the membership change.2026-07-09T21:41:04.617132Z 91699 [Note] [MY-000000] [Galera] Freezing gcache purge at 16198595494
2026-07-09T21:41:04.622051Z 0 [Note] [MY-000000] [Galera] Created page /var/lib/mysql/mysql-data/gcache.page.000000 of size 134217728 bytes
2026-07-09T21:41:14.537019Z 53033 [Note] [MY-010559] [Repl] Multi-threaded replica statistics for channel '': seconds elapsed = 122; events assigned = 110401704; worker queues filled over overrun level = 0; waited due a Worker queue full = 0; waited due the total size = 0; waited at clock conflicts = 7659954362300 waited (count) when Workers occupied = 47189196 waited when Workers occupied = 22964040282100
2026-07-09T21:42:02.139966Z 91770 [Note] [MY-010914] [Server] Aborted connection 91770 to db: 'unconnected' user: 'percona' host: '10.1.163.3' (Got an error reading communication packets).
2026-07-09T21:42:17.061513Z 0 [Note] [MY-000000] [Galera] forgetting c3acab8a-8a74 (ssl://10.1.21.5:4567)
2026-07-09T21:42:17.061596Z 0 [Note] [MY-000000] [Galera] Node 7e3c8f1e-ad75 state primary
2026-07-09T21:42:17.061615Z 0 [Note] [MY-000000] [Galera] Current view of cluster as seen by this node
view (view_id(PRIM,7e3c8f1e-ad75,11)
memb {
        7e3c8f1e-ad75,1
        }
joined {
        }
left {
        }
partitioned {
        c3acab8a-8a74,1
        }
)Although the logs do not explicitly state why purge was frozen, the sequence of events strongly suggests that Galera retained the writesets so the partitioned node could potentially perform an IST when it rejoined.
How could the purge have been frozen?
Further investigation into the codebase and documentation hinted that it is practically impossible that Galera can actually invoke the gcache pages purge pause and only practical way to do it is using:SET GLOBAL wsrep_provider_options='gcache.freeze_purge_at_seqno=XYZ';Reference: https://github.com/percona/galera/pull/132
Related reading: No SST node rejoins in PXC
Reproducing the behavior
Rather than stopping with a theory, Peter Sylvester (SoS) reproduced the behavior in the lab by manually setting gcache.freeze_purge_at_seqno and generating workload with Sysbench. The result matched what we observed in the production.
Additionally, even though gcache.keep_pages_count=3, Galera continued creating additional page files because purging was frozen.mysql&amp;gt; show global status like  'wsrep_last_committed';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| wsrep_last_committed | 94559 |
+----------------------+-------+
1 row in set (0.00 sec)

mysql&amp;gt; SET GLOBAL wsrep_provider_options=&quot;gcache.freeze_purge_at_seqno=94559&quot;;
Query OK, 0 rows affected (0.00 sec)Sysbench was then run on the cluster’s source host to generate logs…[root@CENTOS9-1 ~]# sysbench oltp_read_write --db-driver=mysql --mysql-db=sysbench --mysql-user=sysbench --mysql-password=password --mysql-port=3306 --table_size=1000 --tables=4 --threads=4 --rand-type=uniform --range_size=100 --time=0 --rate=0 --report_interval=5 run
WARNING: Both event and time limits are disabled, running an endless test
sysbench 1.0.20 (using system LuaJIT 2.1.0-beta3)

Running the test with following options:
Number of threads: 4
Report intermediate results every 5 second(s)
Initializing random number generator from current time

Initializing worker threads...
Threads started!

[ 5s ] thds: 4 tps: 311.80 qps: 6246.62 (r/w/o: 4373.81/1248.40/624.40) lat (ms,95%): 16.71 err/s: 0.00 reconn/s: 0.00Following page files are present in datadir[root@CENTOS9-3 ~]# ls -lh /var/lib/mysql/*cache*
-rw-r----- 1 mysql mysql 11M Aug  6 14:14 /var/lib/mysql/galera.cache
-rw-r----- 1 mysql mysql 10M Aug  6 14:14 /var/lib/mysql/gcache.page.000000
-rw-r----- 1 mysql mysql 10M Aug  6 14:14 /var/lib/mysql/gcache.page.000001
-rw-r----- 1 mysql mysql 10M Aug  6 14:15 /var/lib/mysql/gcache.page.000002
-rw-r----- 1 mysql mysql 10M Aug  6 14:15 /var/lib/mysql/gcache.page.000003
-rw-r----- 1 mysql mysql 10M Aug  6 14:15 /var/lib/mysql/gcache.page.000004
-rw-r----- 1 mysql mysql 10M Aug  6 14:16 /var/lib/mysql/gcache.page.000005
-rw-r----- 1 mysql mysql 10M Aug  6 14:16 /var/lib/mysql/gcache.page.000006After the load completed, the mysqld was restarted to observe if the page files were then cleared! But they were not.[root@CENTOS9-3 ~]# systemctl stop mysql
[root@CENTOS9-3 ~]# systemctl start mysql
[root@CENTOS9-3 ~]# ls -lh /var/lib/mysql/*cache*
-rw-r----- 1 mysql mysql 11M Aug  6 14:17 /var/lib/mysql/galera.cache
-rw-r----- 1 mysql mysql 10M Aug  6 14:14 /var/lib/mysql/gcache.page.000000
-rw-r----- 1 mysql mysql 10M Aug  6 14:14 /var/lib/mysql/gcache.page.000001
-rw-r----- 1 mysql mysql 10M Aug  6 14:15 /var/lib/mysql/gcache.page.000002
-rw-r----- 1 mysql mysql 10M Aug  6 14:15 /var/lib/mysql/gcache.page.000003
-rw-r----- 1 mysql mysql 10M Aug  6 14:15 /var/lib/mysql/gcache.page.000004
-rw-r----- 1 mysql mysql 10M Aug  6 14:16 /var/lib/mysql/gcache.page.000005
-rw-r----- 1 mysql mysql 10M Aug  6 14:17 /var/lib/mysql/gcache.page.000006
Another interesting observation
Later the MySQL was restarted, expecting the startup to reclaim the unused pages. It didn’t. Every page file remained on disk after a restart. Even after the explicit configuration of gcache.freeze_purge_at_seqno=-1.
This suggests that startup recovery does not automatically remove these accumulated page files simply because gcache.freeze_purge_at_seqno has been cleared. At least in testing, once purge has been frozen and page files accumulate, restarting MySQL alone is not enough to reclaim the space.
We have a new bug in place for getting this behaviour sorted: PXC-5323
Can the files be deleted?
To answer that, MySQL was stopped, the local GCache files were removed and the node was started again.
Galera recreated the required cache structures automatically. More importantly, the node successfully completed an IST; and in our tests,  deleting the local page files did not force an SST.
Before removing the files, make sure the node is stopped and the cluster has another healthy node that can provide the required writesets. Always validate this behavior in your own environment before using it operationally. Our testing showed that an IST was sufficient.
Conclusion
Based on both the production logs and our lab testing, the accumulation of gcache.page.* files was caused by GCache purging being frozen. Restarting MySQL did not reclaim the accumulated files in our testing. The behavior where a PXC node continuously creates new GCache page files is already fixed under: PXC-4495.
The practical workaround is to stop MySQL, remove the local galera.cache and gcache.page.* files, and start the node again. In our testing, the node rejoined the cluster using IST without requiring a full SST. As always, make sure another healthy node has the required writesets before performing this cleanup.
The post Too many GCache Page Files in MySQL Data Directory appeared first on Percona.</description>
    <content:encoded><![CDATA[<p class="isSelectedEnd">A few thousand <code dir="ltr">gcache.page.*</code> files in a Percona XtraDB Cluster (PXC) data directory is not something you see every day. We came across a case where these files had been accumulating over time and slowly consuming disk space. So, let’s dig into what happened.</p>
<p>At first glance, it looked like GCache had simply stopped cleaning itself up. The investigation started by answering two simple questions: <b>when did the files start appearing,</b> and <b>what changed in the cluster at that time?</b></p>
<h2><b>Finding the starting point</b></h2>
<p>The oldest files showed the issue started on July 9.</p><pre class="urvanov-syntax-highlighter-plain-tag">[hostx] percona@hostx: ~ $ ls -lh /var/lib/mysql/mysql-data/gcache.page.*
-rw-r----- 1 mysql mysql 128M Jul 9 21:42 /var/lib/mysql/mysql-data/gcache.page.000000
-rw-r----- 1 mysql mysql 128M Jul 9 21:42 /var/lib/mysql/mysql-data/gcache.page.000001
-rw-r----- 1 mysql mysql 128M Jul 9 21:42 /var/lib/mysql/mysql-data/gcache.page.000002
...
-rw-r----- 1 mysql mysql 128M Jul 26 07:45 /var/lib/mysql/mysql-data/gcache.page.005976
-rw-r----- 1 mysql mysql 128M Jul 26 07:50 /var/lib/mysql/mysql-data/gcache.page.005977
-rw-r----- 1 mysql mysql 128M Jul 26 07:56 /var/lib/mysql/mysql-data/gcache.page.005978</pre><p>The newest files showed they stopped being created on July 26, which immediately provided a timeline to investigate.</p>
<p>The creation of GCache page files wasn’t random. It started at a specific point in time and stopped after the next MySQL restart.</p>
<h2><b>Can large transactions cause this?</b></h2>
<p>Normally, you don’t see thousands of GCache page files unless Galera cannot reclaim old pages or an exceptionally large writeset forces additional page allocation. In this environment, the GCache ring file was around <b>60 GB</b>, making the large writeset theory very unlikely – actually Impossible! Because there’s a hard limit of the largest transaction size at 2GB.</p>
<p>That pushed the investigation toward the error log, where Galera was found logging:</p><pre class="urvanov-syntax-highlighter-plain-tag">2026-07-09T21:41:04.617132Z 91699 [Note] [MY-000000] [Galera] Freezing gcache purge at 16198595494
2026-07-09T21:41:04.622051Z 0 [Note] [MY-000000] [Galera] Created page /var/lib/mysql/mysql-data/gcache.page.000000 of size 134217728 bytes</pre><p>This was the first strong clue. When gcache.freeze_purge_at_seqno is active, Galera stops reclaiming old GCache pages. As replication continues, new page files are allocated while existing ones remain on disk.</p>
<h2><b>Correlating with cluster activity</b></h2>
<p>Looking a few seconds later in the error log revealed a cluster partition. The timing is difficult to ignore: Galera froze GCache purging, created the first page file, and then recorded the membership change.</p><pre class="urvanov-syntax-highlighter-plain-tag">2026-07-09T21:41:04.617132Z 91699 [Note] [MY-000000] [Galera] Freezing gcache purge at 16198595494
2026-07-09T21:41:04.622051Z 0 [Note] [MY-000000] [Galera] Created page /var/lib/mysql/mysql-data/gcache.page.000000 of size 134217728 bytes
2026-07-09T21:41:14.537019Z 53033 [Note] [MY-010559] [Repl] Multi-threaded replica statistics for channel '': seconds elapsed = 122; events assigned = 110401704; worker queues filled over overrun level = 0; waited due a Worker queue full = 0; waited due the total size = 0; waited at clock conflicts = 7659954362300 waited (count) when Workers occupied = 47189196 waited when Workers occupied = 22964040282100
2026-07-09T21:42:02.139966Z 91770 [Note] [MY-010914] [Server] Aborted connection 91770 to db: 'unconnected' user: 'percona' host: '10.1.163.3' (Got an error reading communication packets).
2026-07-09T21:42:17.061513Z 0 [Note] [MY-000000] [Galera] forgetting c3acab8a-8a74 (ssl://10.1.21.5:4567)
2026-07-09T21:42:17.061596Z 0 [Note] [MY-000000] [Galera] Node 7e3c8f1e-ad75 state primary
2026-07-09T21:42:17.061615Z 0 [Note] [MY-000000] [Galera] Current view of cluster as seen by this node
view (view_id(PRIM,7e3c8f1e-ad75,11)
memb {
        7e3c8f1e-ad75,1
        }
joined {
        }
left {
        }
partitioned {
        c3acab8a-8a74,1
        }
)</pre><p>Although the logs do not explicitly state why purge was frozen, the sequence of events strongly suggests that Galera retained the writesets so the partitioned node could potentially perform an IST when it rejoined.</p>
<h2>How could the purge have been frozen?</h2>
<p>Further investigation into the codebase and documentation hinted that it is practically impossible that Galera can actually invoke the gcache pages purge pause and only practical way to do it is using:</p><pre class="urvanov-syntax-highlighter-plain-tag">SET GLOBAL wsrep_provider_options='gcache.freeze_purge_at_seqno=XYZ';</pre><p>Reference: <a href="https://github.com/percona/galera/pull/132">https://github.com/percona/galera/pull/132</a></p>
<p>Related reading: <a href="https://www.percona.com/blog/no-sst-node-rejoins/">No SST node rejoins in PXC</a></p>
<h2><b>Reproducing the behavior</b></h2>
<p>Rather than stopping with a theory, Peter Sylvester (SoS) reproduced the behavior in the lab by manually setting gcache.freeze_purge_at_seqno and generating workload with Sysbench. The result matched what we observed in the production.</p>
<p>Additionally, even though gcache.keep_pages_count=3, Galera continued creating additional page files because purging was frozen.</p><pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; show global status like  'wsrep_last_committed';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| wsrep_last_committed | 94559 |
+----------------------+-------+
1 row in set (0.00 sec)

mysql&gt; SET GLOBAL wsrep_provider_options="gcache.freeze_purge_at_seqno=94559";
Query OK, 0 rows affected (0.00 sec)</pre><p>Sysbench was then run on the cluster’s source host to generate logs…</p><pre class="urvanov-syntax-highlighter-plain-tag">[root@CENTOS9-1 ~]# sysbench oltp_read_write --db-driver=mysql --mysql-db=sysbench --mysql-user=sysbench --mysql-password=password --mysql-port=3306 --table_size=1000 --tables=4 --threads=4 --rand-type=uniform --range_size=100 --time=0 --rate=0 --report_interval=5 run
WARNING: Both event and time limits are disabled, running an endless test
sysbench 1.0.20 (using system LuaJIT 2.1.0-beta3)

Running the test with following options:
Number of threads: 4
Report intermediate results every 5 second(s)
Initializing random number generator from current time

Initializing worker threads...
Threads started!

[ 5s ] thds: 4 tps: 311.80 qps: 6246.62 (r/w/o: 4373.81/1248.40/624.40) lat (ms,95%): 16.71 err/s: 0.00 reconn/s: 0.00</pre><p>Following page files are present in datadir</p><pre class="urvanov-syntax-highlighter-plain-tag">[root@CENTOS9-3 ~]# ls -lh /var/lib/mysql/*cache*
-rw-r----- 1 mysql mysql 11M Aug  6 14:14 /var/lib/mysql/galera.cache
-rw-r----- 1 mysql mysql 10M Aug  6 14:14 /var/lib/mysql/gcache.page.000000
-rw-r----- 1 mysql mysql 10M Aug  6 14:14 /var/lib/mysql/gcache.page.000001
-rw-r----- 1 mysql mysql 10M Aug  6 14:15 /var/lib/mysql/gcache.page.000002
-rw-r----- 1 mysql mysql 10M Aug  6 14:15 /var/lib/mysql/gcache.page.000003
-rw-r----- 1 mysql mysql 10M Aug  6 14:15 /var/lib/mysql/gcache.page.000004
-rw-r----- 1 mysql mysql 10M Aug  6 14:16 /var/lib/mysql/gcache.page.000005
-rw-r----- 1 mysql mysql 10M Aug  6 14:16 /var/lib/mysql/gcache.page.000006</pre><p>After the load completed, the mysqld was restarted to observe if the page files were then cleared! But they were not.</p><pre class="urvanov-syntax-highlighter-plain-tag">[root@CENTOS9-3 ~]# systemctl stop mysql
[root@CENTOS9-3 ~]# systemctl start mysql
[root@CENTOS9-3 ~]# ls -lh /var/lib/mysql/*cache*
-rw-r----- 1 mysql mysql 11M Aug  6 14:17 /var/lib/mysql/galera.cache
-rw-r----- 1 mysql mysql 10M Aug  6 14:14 /var/lib/mysql/gcache.page.000000
-rw-r----- 1 mysql mysql 10M Aug  6 14:14 /var/lib/mysql/gcache.page.000001
-rw-r----- 1 mysql mysql 10M Aug  6 14:15 /var/lib/mysql/gcache.page.000002
-rw-r----- 1 mysql mysql 10M Aug  6 14:15 /var/lib/mysql/gcache.page.000003
-rw-r----- 1 mysql mysql 10M Aug  6 14:15 /var/lib/mysql/gcache.page.000004
-rw-r----- 1 mysql mysql 10M Aug  6 14:16 /var/lib/mysql/gcache.page.000005
-rw-r----- 1 mysql mysql 10M Aug  6 14:17 /var/lib/mysql/gcache.page.000006</pre><p></p>
<h2><b>Another interesting observation</b></h2>
<p>Later the MySQL was restarted, expecting the startup to reclaim the unused pages. It didn’t. Every page file remained on disk after a restart. Even after the explicit configuration of gcache.freeze_purge_at_seqno=-1.</p>
<p>This suggests that startup recovery does not automatically remove these accumulated page files simply because gcache.freeze_purge_at_seqno has been cleared. At least in testing, once purge has been frozen and page files accumulate, restarting MySQL alone is not enough to reclaim the space.</p>
<p>We have a new bug in place for getting this behaviour sorted: <a href="https://perconadev.atlassian.net/browse/PXC-5323">PXC-5323</a></p>
<h2><b>Can the files be deleted?</b></h2>
<p>To answer that, MySQL was stopped, the local GCache files were removed and the node was started again.</p>
<p>Galera recreated the required cache structures automatically. More importantly, the node successfully completed an <b>IST</b>; and in our tests,  deleting the local page files did <b>not</b> force an SST.</p>
<p>Before removing the files, make sure the node is stopped and the cluster has another healthy node that can provide the required writesets. Always validate this behavior in your own environment before using it operationally. Our testing showed that an IST was sufficient.</p>
<h2><b>Conclusion</b></h2>
<p>Based on both the production logs and our lab testing, the accumulation of gcache.page.* files was caused by GCache purging being frozen. Restarting MySQL did not reclaim the accumulated files in our testing. The behavior where a PXC node continuously creates new GCache page files is already fixed under: <a href="https://perconadev.atlassian.net/browse/PXC-4495">PXC-4495</a>.</p>
<p>The practical workaround is to stop MySQL, remove the local galera.cache and gcache.page.* files, and start the node again. In our testing, the node rejoined the cluster using IST without requiring a full SST. As always, make sure another healthy node has the required writesets before performing this cleanup.</p>
<p>The post <a href="https://www.percona.com/blog/too-many-gcache-page-files-in-mysql-data-directory/">Too many GCache Page Files in MySQL Data Directory</a> appeared first on <a href="https://www.percona.com/">Percona</a>.</p>]]></content:encoded>
    <pubDate>Thu, 17 Sep 2026 05:29:14 +0000</pubDate>
    <dc:creator>Kedar Vaijanapurkar</dc:creator>
    <category>MySQL</category>
    <category>XtraDB Cluster (PXC)</category>
    <category>database administration</category>
    <category>galera</category>
    <category>Galera replication</category>
    <category>Gcache</category>
    <category>gcache purge</category>
    <category>GCache troubleshooting</category>
    <category>gcache.freeze_purge_at_seqno</category>
    <category>gcache.keep_pages_count</category>
    <category>gcache.page</category>
    <category>gcache.page files</category>
    <category>MySQL disk space</category>
    <category>MySQL Troubleshooting</category>
    <category>Percona XtraD</category>
  </item>

  <item>
    <title>Traceability Matters: Gopal Shankar on Opening Up MySQL Development for the Next Decade</title>
    <guid isPermaLink="false">https://www.odbms.org/blog/?p=5970</guid>
    <link>https://www.odbms.org/blog/2026/09/traceability-matters-gopal-shankar-on-opening-up-mysql-development-for-the-next-decade/</link>
    <description>
“Would making this capability available help a meaningful part of the MySQL community build better applications or run better systems?”




Q1. MySQL has recently made available several features from the enterprise tier to the MySQL Community Edition. What is driving this direction?



The simple answer is that Community Edition needs to stay strong for the people who build, run, and depend on MySQL every day. That includes developers, DBAs, startups, large enterprises, and software vendors.



When we bring broadly useful capabilities into Community Edition, particularly in areas like observability, high availability, performance, and developer experience, the whole MySQL ecosystem benefits. Users get a better platform, and we get better feedback from real workloads at scale.



Recent examples include replication observability and Group Replication capabilities, OpenTelemetry support, the Hypergraph Optimizer, Profile-Guided Optimization, and enhanced JSON Duality View support in Community Edition.



I see this as a clear sign of Oracle’s long-term commitment to MySQL. The Community Edition is fundamental to MySQL. Enterprise Edition continues to address additional commercial support, security, and operational needs, but the success of Community Edition is essential to the success of MySQL overall.



Q2. When Oracle makes a feature available in Community Edition, how are those decisions made?



There is not a single checklist that applies to every feature. We start with a practical question: would making this capability available help a meaningful part of the MySQL community build better applications or run better systems?



From there, we look at technical maturity, operational impact, security, compatibility, documentation, and long-term maintainability. A feature has to work well not only in a carefully controlled environment, but also in the many different environments where MySQL is deployed.



The strongest opportunities are often in areas that improve the daily experience of using MySQL: understanding what is happening in the server, operating highly available systems, diagnosing performance problems, and reducing unnecessary complexity for developers. We will continue to evaluate those opportunities as MySQL evolves.



Q3. What does MySQL do better than competitors in community engagement, and where can it improve?



MySQL’s strength is the combination of a mature open source database, deep engineering investment, and an ecosystem that has been built over decades. MySQL runs important workloads for organizations of every size, so reliability, compatibility, upgrades, tooling, and operational simplicity matter deeply to our community.



We also see opportunities to improve. The best open source communities make it easy for people to understand how an idea moves from discussion to action. We want to improve that path in MySQL through clearer design discussions, better issue triage, more visible contribution paths, and more predictable review and feedback.



Code is important, but it is not the only meaningful contribution. Testing, documentation, benchmarking, production feedback, tools, and community education all make MySQL better. We want contributors to feel that these forms of participation are recognized and useful.



Q4. MySQL 9.7.0 LTS brings capabilities previously limited to MySQL Enterprise Edition—including JSON Duality Views, the Hypergraph Optimizer, and improvements to replication observability and HA behavior—into MySQL Community Edition. Which of these changes do you think will have the greatest real-world impact for DBAs and developers, and what should teams consider before adopting them in production?



I would separate immediate operational impact from longer-term developer impact. For DBAs running Group Replication, the replication observability and HA changes will probably have the fastest and broadest benefit. Better visibility into flow control, applier lag and throughput, unhealthy members, and primary election helps teams diagnose problems earlier and make failover behavior more predictable. This is practical day-to-day value, especially for teams operating clusters at scale.



For developers, JSON Duality Views may be the more consequential change over time. They let teams work with JSON documents while retaining relational integrity and a single source of truth. The Hypergraph Optimizer can also be significant for complex queries, but its benefit will vary more by workload.



Teams should approach an LTS upgrade with thorough validation. Test representative workloads, failure scenarios, replication behavior, upgrades, and monitoring integrations in staging first. For the Hypergraph Optimizer, compare plans and performance for important queries. For JSON Duality Views, validate the data model, update paths, permissions, and concurrency behavior. And for telemetry, make sure the collector, retention policy, and handling of potentially sensitive operational data are ready before turning it on in production. The Community Edition additions cover replication and HA behavior, telemetry, JSON Duality Views, and the Hypergraph Optimizer.



Q5. You were personally involved in designing JSON Duality Views. What problem does it solve?



JSON Duality Views solve a problem many application teams know well. Developers often prefer JSON because it maps naturally to APIs and application objects. But relational modeling gives them normalization, transactional consistency, referential integrity, and SQL.



Historically, teams often had to choose one model, or build and maintain their own mapping layer between application objects and relational tables. In some cases, they also ended up duplicating data across multiple systems.



JSON Duality Views let an application work with hierarchical JSON documents while the underlying data remains relational. The application can use the model that feels natural for the task, but MySQL still provides a single source of truth.



For a team, that can mean less mapping code and simpler synchronization. It does not remove the need for good schema or API design, and it will not fit every application, but it gives suitable workloads a much simpler way to combine document-style development with relational strengths.



Q6. How do you balance new capabilities with MySQL’s simplicity and reliability?



MySQL has earned trust because it is practical. People can deploy it, operate it, upgrade it, and troubleshoot it with confidence. New capabilities must preserve that experience.



We pay close attention to defaults, configuration, backward compatibility, documentation, and operational behavior. Early Access builds, LTS releases, compatibility testing, and upgrade guidance are the practical mechanisms that help us validate that balance before broad adoption. The MySQL 9.6 foreign-key work is a good example. We moved foreign-key checks and cascades into the SQL layer so that those changes are visible to binary logs and CDC tools, while preserving compatibility, validating performance, and providing a temporary `innodb_native_foreign_keys` fallback for staged adoption. A feature should be powerful when users need it, while preserving the straightforward core MySQL experience for everyone else. 



Not every user needs every new capability. Success is giving developers and DBAs useful new options while maintaining the stable, predictable MySQL experience that existing users rely on.



Q7. What does the more open community model look like in practice?



For a developer or DBA, participation should not begin only when they have a patch ready. They can discuss roadmap topics, share use cases, test Early Access releases, file actionable bugs, join GitHub discussions, contribute documentation or benchmarks, and participate in community events and contributor summits.



The important change is connecting these activities more clearly. If someone raises a good issue or proposal, they should be able to see where it goes next. Does it become a design discussion? A bug investigation? A request for testing? A roadmap input? That traceability matters.



Over the coming releases and community cycles, the community should see clearer guidance, more public technical discussion, improved GitHub workflows, and more structured ways to engage early. What matters most is whether people find the process easier to use and receive useful follow-up.



Q8. What changes are being made to improve the contributor experience, and how will you measure success?



The first improvement is clarity. Contributors need to know where to start, what information is needed, how review works, and what happens if a proposal is not accepted as submitted. 



We are working toward clearer templates, better-defined contribution paths, more visible technical discussions, and stronger links between issues, proposals, patches, and bug records. This should make it easier for contributors and users to follow the progress of an idea.



The second improvement is feedback. When a contribution needs further refinement, people should receive a clear outcome, and where possible, practical guidance about what to do next. We will look at evidence: response and resolution trends, time to initial triage, review cycle time, contributor growth, contribution quality, roadmap participation, Early Access adoption, and feedback from contributors. We also intend to share progress regularly, pairing timely acknowledgment with meaningful follow-through.The goal is not simply to collect more pull requests. It is to create a community process that produces better outcomes.



Q9. MySQL recently marked 30 years. What will define the next decade?



MySQL must remain the practical, dependable choice for the application workloads that matter most: transactional systems, cloud-native services, distributed applications, and data-intensive workloads.



That means continued investment in performance, high availability, observability, security, developer productivity, and operational simplicity. These may not always be the most visible areas of innovation, but they are the reasons people trust a database in production.



The other important aspect is community participation. MySQL cannot thrive for another decade based only on work from one company. The community can influence priorities earlier, contribute effectively, build tools and extensions, share operational knowledge, and see that its feedback leads to visible action.



By the time MySQL turns 40, I would like it to be known not only for scale and reliability, but also for a community that has a real and practical role in shaping its future.



Qx. Anything else you wish to add?



I would encourage people to engage with MySQL early and directly. Try the Early Access releases, share concrete production experience, bring specific use cases, and tell us where the friction is.



The most useful feedback is grounded in real workloads and comes with enough detail for us to act on it. MySQL has always evolved through the combined work of engineers, users, customers, partners, and contributors. We want the next chapter to be even more collaborative.



………………………………………………………..







Gopal Shankar, Director of MySQL Engineering, Oracle.For over 20 years, I have worked at the heart of database engine architecture. Currently, as the Director of MySQL Engineering, I lead the organization responsible for the strategy, development, and roadmap of one of the world’s most popular database platforms. My expertise lies in the core internals of MySQL specifically kernel-level development, performance tuning, and scalability. I believe in solving complex technical challenges by prioritizing architectural simplicity and resilience. I have led the design of several important features, including the MySQL 8.0 Data Dictionary and Information Schema, as well as the recent JSON Duality feature. Additionally, I have helped architect the integration of foreign key handling directly into the SQL layer, effectively resolving long-standing trigger cascade limitations in MySQL recently. My goal is to deliver features that are high-performing, reliable and developer-friendly. I am focusing towards executing the MySQL roadmap and ensuring MySQL platform remains a powerful, solid foundation for modern applications. Beyond strategy, I stay connected to the kernel-level complexities tackling issues like high CPU usage, database corruption, and throughput bottlenecks. I am focused on fostering technical excellence and delivering a database engine that evolves with the needs of the industry.https://www.linkedin.com/in/gopal-shankar-1b34664/



………………….



Follow us on X



Follow us on LinkedIn



</description>
    <content:encoded><![CDATA[<blockquote class="wp-block-quote">
<p>“Would making this capability available help a meaningful part of the MySQL community build better applications or run better systems?”</p>
</blockquote>



<p><strong>Q1. MySQL has recently made available several features from the enterprise tier to the MySQL Community Edition. What is driving this direction?</strong></p>



<p>The simple answer is that Community Edition needs to stay strong for the people who build, run, and depend on MySQL every day. That includes developers, DBAs, startups, large enterprises, and software vendors.</p>



<p>When we bring broadly useful capabilities into Community Edition, particularly in areas like observability, high availability, performance, and developer experience, the whole MySQL ecosystem benefits. Users get a better platform, and we get better feedback from real workloads at scale.</p>



<p>Recent examples include replication observability and Group Replication capabilities, <a href="https://blogs.oracle.com/mysql/a-new-era-of-mysql-monitoring-opentelemetry-metrics-with-prometheus" data-type="URL" data-id="https://blogs.oracle.com/mysql/a-new-era-of-mysql-monitoring-opentelemetry-metrics-with-prometheus" target="_blank" rel="noreferrer noopener">OpenTelemetry support</a>, the Hypergraph Optimizer, Profile-Guided Optimization, and enhanced JSON Duality View support in Community Edition.</p>



<p>I see this as a clear sign of Oracle’s long-term commitment to MySQL. The Community Edition is fundamental to MySQL. Enterprise Edition continues to address additional commercial support, security, and operational needs, but the success of Community Edition is essential to the success of MySQL overall.</p>



<p><strong>Q2. When Oracle makes a feature available in Community Edition, how are those decisions made?</strong></p>



<p>There is not a single checklist that applies to every feature. We start with a practical question: would making this capability available help a meaningful part of the MySQL community build better applications or run better systems?</p>



<p>From there, we look at technical maturity, operational impact, security, compatibility, documentation, and long-term maintainability. A feature has to work well not only in a carefully controlled environment, but also in the many different environments where MySQL is deployed.</p>



<p>The strongest opportunities are often in areas that improve the daily experience of using MySQL: understanding what is happening in the server, operating highly available systems, diagnosing performance problems, and reducing unnecessary complexity for developers. We will continue to evaluate those opportunities as MySQL evolves.</p>



<p><strong>Q3. What does MySQL do better than competitors in community engagement, and where can it improve?</strong></p>



<p>MySQL’s strength is the combination of a mature open source database, deep engineering investment, and an ecosystem that has been built over decades. MySQL runs important workloads for organizations of every size, so reliability, compatibility, upgrades, tooling, and operational simplicity matter deeply to our community.</p>



<p>We also see opportunities to improve. The best open source communities make it easy for people to understand how an idea moves from discussion to action. We want to improve that path in MySQL through clearer design discussions, better issue triage, more visible contribution paths, and more predictable review and feedback.</p>



<p>Code is important, but it is not the only meaningful contribution. Testing, documentation, benchmarking, production feedback, tools, and community education all make MySQL better. We want contributors to feel that these forms of participation are recognized and useful.</p>



<p><strong>Q4. MySQL 9.7.0 LTS brings capabilities previously limited to MySQL Enterprise Edition—including JSON Duality Views, the Hypergraph Optimizer, and improvements to replication observability and HA behavior—into MySQL Community Edition. Which of these changes do you think will have the greatest real-world impact for DBAs and developers, and what should teams consider before adopting them in production?</strong></p>



<p>I would separate immediate operational impact from longer-term developer impact. For DBAs running Group Replication, the replication observability and HA changes will probably have the fastest and broadest benefit. Better visibility into flow control, applier lag and throughput, unhealthy members, and primary election helps teams diagnose problems earlier and make failover behavior more predictable. This is practical day-to-day value, especially for teams operating clusters at scale.</p>



<p>For developers, <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/jsnvu/overview-json-relational-duality-views.html" data-type="URL" data-id="https://docs.oracle.com/en/database/oracle/oracle-database/26/jsnvu/overview-json-relational-duality-views.html" target="_blank" rel="noreferrer noopener">JSON Duality Views</a> may be the more consequential change over time. They let teams work with JSON documents while retaining relational integrity and a single source of truth. The Hypergraph Optimizer can also be significant for complex queries, but its benefit will vary more by workload.</p>



<p>Teams should approach an LTS upgrade with thorough validation. Test representative workloads, failure scenarios, replication behavior, upgrades, and monitoring integrations in staging first. For the <a href="https://blogs.oracle.com/mysql/the-hypergraph-optimizer-is-now-available-in-mysql-9-7-community-edition" data-type="URL" data-id="https://blogs.oracle.com/mysql/the-hypergraph-optimizer-is-now-available-in-mysql-9-7-community-edition" target="_blank" rel="noreferrer noopener">Hypergraph Optimizer</a>, compare plans and performance for important queries. For JSON Duality Views, validate the data model, update paths, permissions, and concurrency behavior. And for telemetry, make sure the collector, retention policy, and handling of potentially sensitive operational data are ready before turning it on in production. The Community Edition additions cover replication and HA behavior, telemetry, JSON Duality Views, and the Hypergraph Optimizer.</p>



<p><strong>Q5. You were personally involved in designing JSON Duality Views. What problem does it solve?</strong></p>



<p>JSON Duality Views solve a problem many application teams know well. Developers often prefer JSON because it maps naturally to APIs and application objects. But relational modeling gives them normalization, transactional consistency, referential integrity, and SQL.</p>



<p>Historically, teams often had to choose one model, or build and maintain their own mapping layer between application objects and relational tables. In some cases, they also ended up duplicating data across multiple systems.</p>



<p>JSON Duality Views let an application work with hierarchical JSON documents while the underlying data remains relational. The application can use the model that feels natural for the task, but MySQL still provides a single source of truth.</p>



<p>For a team, that can mean less mapping code and simpler synchronization. It does not remove the need for good schema or API design, and it will not fit every application, but it gives suitable workloads a much simpler way to combine document-style development with relational strengths.</p>



<p><br><strong>Q6. How do you balance new capabilities with MySQL’s simplicity and reliability?</strong></p>



<p>MySQL has earned trust because it is practical. People can deploy it, operate it, upgrade it, and troubleshoot it with confidence. New capabilities must preserve that experience.</p>



<p class="has-text-align-left">We pay close attention to defaults, configuration, backward compatibility, documentation, and operational behavior. Early Access builds, LTS releases, compatibility testing, and upgrade guidance are the practical mechanisms that help us validate that balance before broad adoption. The MySQL 9.6 foreign-key work is a good example. We moved foreign-key checks and cascades into the SQL layer so that those changes are visible to binary logs and CDC tools, while preserving compatibility, validating performance, and providing a temporary `innodb_native_foreign_keys` fallback for staged adoption. A feature should be powerful when users need it, while preserving the straightforward core MySQL experience for everyone else. </p>



<p>Not every user needs every new capability. Success is giving developers and DBAs useful new options while maintaining the stable, predictable MySQL experience that existing users rely on.</p>



<p><strong>Q7. What does the more open community model look like in practice?</strong></p>



<p>For a developer or DBA, participation should not begin only when they have a patch ready. They can discuss roadmap topics, share use cases, test Early Access releases, file actionable bugs, join GitHub discussions, contribute documentation or benchmarks, and participate in community events and contributor summits.</p>



<p>The important change is connecting these activities more clearly. If someone raises a good issue or proposal, they should be able to see where it goes next. Does it become a design discussion? A bug investigation? A request for testing? A roadmap input? That traceability matters.</p>



<p>Over the coming releases and community cycles, the community should see clearer guidance, more public technical discussion, improved GitHub workflows, and more structured ways to engage early. What matters most is whether people find the process easier to use and receive useful follow-up.</p>



<p><strong>Q8. What changes are being made to improve the contributor experience, and how will you measure success?</strong></p>



<p>The first improvement is clarity. Contributors need to know where to start, what information is needed, how review works, and what happens if a proposal is not accepted as submitted. <strong></strong></p>



<p>We are working toward clearer templates, better-defined contribution paths, more visible technical discussions, and stronger links between issues, proposals, patches, and bug records. This should make it easier for contributors and users to follow the progress of an idea.</p>



<p>The second improvement is feedback. When a contribution needs further refinement, people should receive a clear outcome, and where possible, practical guidance about what to do next. <br>We will look at evidence: response and resolution trends, time to initial triage, review cycle time, contributor growth, contribution quality, roadmap participation, Early Access adoption, and feedback from contributors. We also intend to share progress regularly, pairing timely acknowledgment with meaningful follow-through.The goal is not simply to collect more pull requests. It is to create a community process that produces better outcomes.</p>



<p><strong>Q9. MySQL recently marked 30 years. What will define the next decade?</strong></p>



<p>MySQL must remain the practical, dependable choice for the application workloads that matter most: transactional systems, cloud-native services, distributed applications, and data-intensive workloads.</p>



<p>That means continued investment in performance, high availability, observability, security, developer productivity, and operational simplicity. These may not always be the most visible areas of innovation, but they are the reasons people trust a database in production.</p>



<p>The other important aspect is community participation. MySQL cannot thrive for another decade based only on work from one company. The community can influence priorities earlier, contribute effectively, build tools and extensions, share operational knowledge, and see that its feedback leads to visible action.</p>



<p>By the time MySQL turns 40, I would like it to be known not only for scale and reliability, but also for a community that has a real and practical role in shaping its future.</p>



<p><strong>Qx. Anything else you wish to add?</strong></p>



<p>I would encourage people to engage with MySQL early and directly. Try the Early Access releases, share concrete production experience, bring specific use cases, and tell us where the friction is.</p>



<p>The most useful feedback is grounded in real workloads and comes with enough detail for us to act on it. MySQL has always evolved through the combined work of engineers, users, customers, partners, and contributors. We want the next chapter to be even more collaborative.</p>



<p>………………………………………………………..</p>



<figure class="wp-block-image size-full is-resized"><a href="https://www.odbms.org/blog/wp-content/uploads/2026/09/Gopal-Shankar-headshot.jpeg"><img decoding="async" src="https://www.odbms.org/blog/wp-content/uploads/2026/09/Gopal-Shankar-headshot.jpeg" alt="" class="wp-image-5971" width="281" height="281" srcset="https://www.odbms.org/blog/wp-content/uploads/2026/09/Gopal-Shankar-headshot.jpeg 340w, https://www.odbms.org/blog/wp-content/uploads/2026/09/Gopal-Shankar-headshot-300x300.jpeg 300w, https://www.odbms.org/blog/wp-content/uploads/2026/09/Gopal-Shankar-headshot-150x150.jpeg 150w" sizes="(max-width: 281px) 100vw, 281px"></a></figure>



<p><strong>Gopal Shankar, </strong>Director of MySQL Engineering, Oracle.<br><br>For over 20 years, I have worked at the heart of database engine architecture. Currently, as the Director of MySQL Engineering, I lead the organization responsible for the strategy, development, and roadmap of one of the world’s most popular database platforms. My expertise lies in the core internals of MySQL specifically kernel-level development, performance tuning, and scalability. I believe in solving complex technical challenges by prioritizing architectural simplicity and resilience. I have led the design of several important features, including the MySQL 8.0 Data Dictionary and Information Schema, as well as the recent JSON Duality feature. Additionally, I have helped architect the integration of foreign key handling directly into the SQL layer, effectively resolving long-standing trigger cascade limitations in MySQL recently. My goal is to deliver features that are high-performing, reliable and developer-friendly. I am focusing towards executing the MySQL roadmap and ensuring MySQL platform remains a powerful, solid foundation for modern applications. Beyond strategy, I stay connected to the kernel-level complexities tackling issues like high CPU usage, database corruption, and throughput bottlenecks. I am focused on fostering technical excellence and delivering a database engine that evolves with the needs of the industry.<br><br><a href="https://www.linkedin.com/in/gopal-shankar-1b34664/">https://www.linkedin.com/in/gopal-shankar-1b34664/</a></p>



<p>………………….</p>



<p><a href="https://x.com/odbmsorg"><strong>Follow us on X</strong></a></p>



<p><a href="https://www.linkedin.com/in/roberto-v-zicari-087863/"><strong>Follow us on LinkedIn</strong></a></p>



<p><br><br></p>]]></content:encoded>
    <pubDate>Wed, 16 Sep 2026 14:07:28 +0000</pubDate>
    <dc:creator>Roberto V. Zicari</dc:creator>
    <category>Uncategorized</category>
    <category>Gopal Shankar</category>
    <category>JSON Duality Views</category>
    <category>MySQL</category>
    <category>MySQL Community Edition</category>
    <category>MySQL Enterprise Edition</category>
    <category>open source</category>
    <category>Oracle</category>
  </item>

  <item>
    <title>Vibe Coding a Database Lab: How I Built DBCanvas to Stop Rebuilding the Same Test Environment</title>
    <guid isPermaLink="false">https://percona.community/blog/2026/09/16/vibe-coding-a-database-lab/</guid>
    <link>https://percona.community/blog/2026/09/16/vibe-coding-a-database-lab/</link>
    <description>Percona gives us room to work on our own AI-assisted projects, and I used mine to fix a problem I kept running into. Every time I wanted to try a new database feature, debug something tricky, or reproduce a customer issue, I ended up rebuilding much of the same infrastructure: DNS, TLS, Docker networking, database topologies, users and test data. Over the years, I wrote scripts to automate this but I still found myself copying and pasting post-installation steps from one lab to the next.
That repetition is what turned into DBCanvas, a self-hosted lab for designing, deploying, operating, and stress-testing multi-node database stacks on my own machine. Just design a topology on a canvas, click Deploy, and get real running nodes connected to the services and supporting infrastructure your test requires. Then, use the tools built into it or third party tools to load those databases, watch them work, and figure out why they’re misbehaving.
The code is up at github.com/jaimesicam/dbcanvas.


Figure 1: Deploying a multi-node MySQL topology with monitoring and orchestration
It’s vibe coded, and that’s the point
I want to be upfront about this that DBCanvas is vibe coded and built conversationally with an AI coding assistant rather than hand-written line by line. That turned out to be exactly the right approach for a tool whose whole job is to remove setup friction.
My initial loop or workflow looked like this. I started by asking the assistant to generate UI/UX demos for the frontend with React, a backend with Go, a drag-and-drop node canvas, a user management system and I iterated it until it felt right. Once I was satisfied, I asked it to turn those requirements into a SCAFFOLD.md which contained a full blueprint precise enough for the coding agent to rebuild the app from scratch as it contained the tech stack, naming conventions, directory tree, backend behavior, frontend behavior and the interactive details of the node editor itself.


Figure 2: The initial interface prototype that established the visual direction


Figure 3: The initial node-editor prototype for composing connected services
From there, I added features incrementally, budgeted by whatever tokens I had available in a session and logged every change in an IMPLEMENTATION.md so that I have a record of what it took to go from the original scaffold to wherever the project currently stood.


Figure 4: IMPLEMENTATION.md records each feature added after the initial scaffold
If I ever needed to rebuild the project from nothing, those two files are essentially the whole story. I know it is crude, but it’s my first time building with this many moving parts. It has held up so far at least for me.
The current loop looks like this:

Hit friction while testing, debugging, or learning something new.
Describe the environment that would remove that friction.
Let the assistant scaffold the automation: versions, configuration, identity, data, and tooling for that environment.
Keep whatever turned out to be reusable inside DBCanvas for next time.
Turn the whole workflow into something you can drive from a browser.

When Percona Server 8.4.11-11 shipped OpenID Connect authentication, I didn’t want to manually set up a Keycloak instance, wire up realms and clients, create sample identities, and configure Percona Server’s OIDC plugin every time I wanted to poke at it. Instead, that became a new DBCanvas setup. Deploy Keycloak + Percona Server + sample identities with a few clicks. You can inspect the generated OIDC configuration, authenticate with an ID token, and verify the mapped MySQL role, all without setting this up yourself. Vibe coding is what made it fast enough to build that scaffolding the same day the feature landed, and DBCanvas turned it into a reusable setup I can deploy again whenever I need it.


Figure 5: A Percona Server OIDC lab with Keycloak and a success mapped-role login
It’s not just about one feature… it’s a whole lab
OIDC with Keycloak is just one example. DBCanvas can also build a broader range of database environments:

MySQL: Percona XtraDB Cluster, Percona Server, MySQL Community, asynchronous replication, InnoDB Cluster and Group Replication, and MariaDB
PostgreSQL: standalone, Patroni, repmgr, Spock multi-master, CloudNativePG or Crunchy PGO on Kubernetes
MongoDB: Percona Server for MongoDB as standalone, replica set, or sharded cluster
Valkey: standalone and cluster

And around them, the infrastructure that makes a stack behave like a real environment. An Intranet node can provide DNS, mail, OpenLDAP, a Squid proxy and a certificate authority. Other nodes provide PMM, ProxySQL, HAProxy, Orchestrator, SeaweedFS S3, Keycloak, OpenBao, Samba AD DC, and a Kubernetes frame that runs any of six database operators.
A useful lab also needs activity. DBCanvas ships application simulators for a hotel booking system, an airline, a car rental fleet, and a stock exchange. Spin up a MongoDB replica set and point the stock market simulator at it and writes begin flowing through the set. You can observe elections and inspect real oplog activity instead of manually generating load against an idle cluster.


Figure 6: A MongoDB replica set running the stock market simulator with diagnostic tools attached
Leaning on tools other people already built
DBCanvas can make deployments more useful by integrating tools my colleagues have built for managing, troubleshooting and analyzing database environments. I can add them as part of the deployment workflow and place it alongside the database it is intended to work with.

MClusterAdmin, a MongoDB administration panel created by Przemek Malkowski, runs as its own node alongside a MongoDB deployment. It displays topology and replica-set status, sharding and the balancer, slow queries with explain, indexes, users and roles, all in a browser tab.



Figure 7: MClusterAdmin displaying the replica-set state for a DBCanvas deployment

Big Hole, an FTDC viewer, developed by Zelmar Michelini that decodes diagnostic.data. Drag in a folder and Big Hole charts every metric it can find. In DBCanvas, collecting the data is just as straightforward: right-click a MongoDB node, open the context menu, and download a compressed archive containing its logs and diagnostic data. Simply decompress the archive to your download directory, then drag the folder into Big Hole to visualize FTDC metrics alongside related log events.



Figure 8: Big Hole visualizing FTDC metrics downloaded from a MongoDB node
The same principle applies to deeper diagnostic work. DBCanvas automates the setup around proven tools instead of replacing them.
For example, the Operator Debugger uses Delve to step through Kubernetes operators with breakpoints, call stacks and variables.


Figure 9: Operator Debugger paused at a breakpoint inside a Percona operator
There’s also the Core Dump Analyzer where you can mount a core dump and matching binaries read-only, then inspect it with GDB.


Figure 10: Web-based Core Dump Analyzer with equivalent terminal command for manual troubleshooting
Ease of Use
While DBCanvas makes it easy to deploy environments but for troubleshooting, you still need to look deeper into the implementation within the nodes. Accessing the deployment via web terminal, regular terminal or web-based Filemanager would be helpful to have command of the deployment.


Figure 11: The file manager inspecting a MongoDB configuration inside a deployed node
The node menu provides the exact Docker exec command for direct access from a regular terminal.


Figure 12: Direct container access from a Docker exec command copied from the node menu
Testing beyond deployment
DBCanvas also comes with a data generator, a parallel query runner, a benchmark tool (OLTP/OLAP, read-write and read-only), and a packet inspector that decodes MySQL, PostgreSQL, MongoDB and Valkey traffic off the wire.


Figure 13: Data Generator creating sample rows for a deployed database


Figure 14: Packet Inspector decoding MongoDB traffic from the lab network
Together, these tools help reproduce problems facing a real deployment as I can generate data, apply load, inspect queries and examine network traffic.
Try it

bash
Copy
Copied!



git clone https://github.com/jaimesicam/dbcanvas.git &amp;amp;&amp;amp; cd dbcanvas
make install


That builds the node images and starts DBCanvas at http://localhost:8080. The first run takes a while since it’s building docker images from scratch and learning which software versions are available for each OS.
Three important caveats:
DBCanvas creates disposable labs for previewing, testing and learning. It’s not a production software. It uses default credentials and favors setup speed over production security.
My workstation remains its primary test environment. If you run it elsewhere and encounter a bug or rough edge, please open a GitHub issue.
Some features are still maturing. I chose to bring many capabilities into one project, which means a few areas still need additional refinement and testing. For example, the Core Dump analyzer could benefit from more sophisticated variable extraction as well as the automatic detection of the appropriate operating system and compatible debug libraries to deploy.
If you’ve ever rebuilt the same test cluster for the third time this month, or wished you could hand a colleague a working reproduction instead of a page of setup instructions, try DBCanvas as it might save you some of the time it has saved me.</description>
    <content:encoded><![CDATA[<p>Percona gives us room to work on our own AI-assisted projects, and I used mine to fix a problem I kept running into. Every time I wanted to try a new database feature, debug something tricky, or reproduce a customer issue, I ended up rebuilding much of the same infrastructure: DNS, TLS, Docker networking, database topologies, users and test data. Over the years, I wrote scripts to automate this but I still found myself copying and pasting post-installation steps from one lab to the next.</p>
<p>That repetition is what turned into <a href="https://github.com/jaimesicam/dbcanvas" target="_blank" rel="noopener noreferrer">DBCanvas</a>, a self-hosted lab for designing, deploying, operating, and stress-testing multi-node database stacks on my own machine. Just design a topology on a canvas, click Deploy, and get real running nodes connected to the services and supporting infrastructure your test requires. Then, use the tools built into it or third party tools to load those databases, watch them work, and figure out why they’re misbehaving.</p>
<p>The code is up at <a href="https://github.com/jaimesicam/dbcanvas" target="_blank" rel="noopener noreferrer">github.com/jaimesicam/dbcanvas</a>.</p>
<p>
<figure><img width="1678" height="1004" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/dbcanvas-mysql-topology_hu_76d5cfdbaaba475d.webp 768w, https://percona.community/blog/2026/09/dbcanvas-mysql-topology_hu_de165278c539f3fe.webp 480w, https://percona.community/blog/2026/09/dbcanvas-mysql-topology_hu_5854cf9bde1b4546.webp 1400w" src="https://percona.community/blog/2026/09/dbcanvas-mysql-topology_hu_5854cf9bde1b4546.webp" alt=" " loading="lazy"></figure></p>
<p><em>Figure 1: Deploying a multi-node MySQL topology with monitoring and orchestration</em></p>
<h2>It’s vibe coded, and that’s the point</h2>
<p>I want to be upfront about this that DBCanvas is vibe coded and built conversationally with an AI coding assistant rather than hand-written line by line. That turned out to be exactly the right approach for a tool whose whole job is to remove setup friction.</p>
<p>My initial loop or workflow looked like this. I started by asking the assistant to generate UI/UX demos for the frontend with React, a backend with Go, a drag-and-drop node canvas, a user management system and I iterated it until it felt right. Once I was satisfied, I asked it to turn those requirements into a <code>SCAFFOLD.md</code> which contained a full blueprint precise enough for the coding agent to rebuild the app from scratch as it contained the tech stack, naming conventions, directory tree, backend behavior, frontend behavior and the interactive details of the node editor itself.</p>
<p>
<figure><img width="1783" height="841" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/dbcanvas-ui-prototype_hu_1177929d5abef1ba.webp 768w, https://percona.community/blog/2026/09/dbcanvas-ui-prototype_hu_fccb1c21b82f72.webp 480w, https://percona.community/blog/2026/09/dbcanvas-ui-prototype_hu_3f0f68d5369a8831.webp 1400w" src="https://percona.community/blog/2026/09/dbcanvas-ui-prototype_hu_3f0f68d5369a8831.webp" alt=" " loading="lazy"></figure></p>
<p><em>Figure 2: The initial interface prototype that established the visual direction</em></p>
<p>
<figure><img width="1485" height="657" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/dbcanvas-node-editor-prototype_hu_ce3b4f5e429934b1.webp 768w, https://percona.community/blog/2026/09/dbcanvas-node-editor-prototype_hu_96a0cdfac3b9d1fe.webp 480w, https://percona.community/blog/2026/09/dbcanvas-node-editor-prototype_hu_e6ecbcb1d23abb2.webp 1400w" src="https://percona.community/blog/2026/09/dbcanvas-node-editor-prototype_hu_e6ecbcb1d23abb2.webp" alt=" " loading="lazy"></figure></p>
<p><em>Figure 3: The initial node-editor prototype for composing connected services</em></p>
<p>From there, I added features incrementally, budgeted by whatever tokens I had available in a session and logged every change in an <code>IMPLEMENTATION.md</code> so that I have a record of what it took to go from the original scaffold to wherever the project currently stood.</p>
<p>
<figure><img width="1431" height="1027" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/dbcanvas-implementation-md_hu_4c2402290e8fbd50.webp 768w, https://percona.community/blog/2026/09/dbcanvas-implementation-md_hu_8067c19e72192095.webp 480w, https://percona.community/blog/2026/09/dbcanvas-implementation-md_hu_f030e69352e74b8c.webp 1400w" src="https://percona.community/blog/2026/09/dbcanvas-implementation-md_hu_f030e69352e74b8c.webp" alt=" " loading="lazy"></figure></p>
<p><em>Figure 4: IMPLEMENTATION.md records each feature added after the initial scaffold</em></p>
<p>If I ever needed to rebuild the project from nothing, those two files are essentially the whole story. I know it is crude, but it’s my first time building with this many moving parts. It has held up so far at least for me.</p>
<p>The current loop looks like this:</p>
<ol>
<li>Hit friction while testing, debugging, or learning something new.</li>
<li>Describe the environment that would remove that friction.</li>
<li>Let the assistant scaffold the automation: versions, configuration, identity, data, and tooling for that environment.</li>
<li>Keep whatever turned out to be reusable inside DBCanvas for next time.</li>
<li>Turn the whole workflow into something you can drive from a browser.</li>
</ol>
<p>When Percona Server 8.4.11-11 shipped OpenID Connect authentication, I didn’t want to manually set up a Keycloak instance, wire up realms and clients, create sample identities, and configure Percona Server’s OIDC plugin every time I wanted to poke at it. Instead, that became a new DBCanvas setup. Deploy Keycloak + Percona Server + sample identities with a few clicks. You can inspect the generated OIDC configuration, authenticate with an ID token, and verify the mapped MySQL role, all without setting this up yourself. Vibe coding is what made it fast enough to build that scaffolding the same day the feature landed, and DBCanvas turned it into a reusable setup I can deploy again whenever I need it.</p>
<p>
<figure><img width="1622" height="1025" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/dbcanvas-oidc-keycloak_hu_f9702a8157b4fafd.webp 768w, https://percona.community/blog/2026/09/dbcanvas-oidc-keycloak_hu_3fd901f7e1b2aefb.webp 480w, https://percona.community/blog/2026/09/dbcanvas-oidc-keycloak_hu_f57326a16f706a8f.webp 1400w" src="https://percona.community/blog/2026/09/dbcanvas-oidc-keycloak_hu_f57326a16f706a8f.webp" alt=" " loading="lazy"></figure></p>
<p><em>Figure 5: A Percona Server OIDC lab with Keycloak and a success mapped-role login</em></p>
<h2>It’s not just about one feature… it’s a whole lab</h2>
<p>OIDC with Keycloak is just one example. DBCanvas can also build a broader range of database environments:</p>
<ul>
<li><strong>MySQL:</strong> Percona XtraDB Cluster, Percona Server, MySQL Community, asynchronous replication, InnoDB Cluster and Group Replication, and MariaDB</li>
<li><strong>PostgreSQL:</strong> standalone, Patroni, repmgr, Spock multi-master, CloudNativePG or Crunchy PGO on Kubernetes</li>
<li><strong>MongoDB:</strong> Percona Server for MongoDB as standalone, replica set, or sharded cluster</li>
<li><strong>Valkey:</strong> standalone and cluster</li>
</ul>
<p>And around them, the infrastructure that makes a stack behave like a real environment. An Intranet node can provide DNS, mail, OpenLDAP, a Squid proxy and a certificate authority. Other nodes provide PMM, ProxySQL, HAProxy, Orchestrator, SeaweedFS S3, Keycloak, OpenBao, Samba AD DC, and a Kubernetes frame that runs any of six database operators.</p>
<p>A useful lab also needs activity. DBCanvas ships application simulators for a hotel booking system, an airline, a car rental fleet, and a stock exchange. Spin up a MongoDB replica set and point the stock market simulator at it and writes begin flowing through the set. You can observe elections and inspect real oplog activity instead of manually generating load against an idle cluster.</p>
<p>
<figure><img width="1858" height="934" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/dbcanvas-mongodb-stock-market_hu_d27140ebfe284033.webp 768w, https://percona.community/blog/2026/09/dbcanvas-mongodb-stock-market_hu_38a52a694316fddb.webp 480w, https://percona.community/blog/2026/09/dbcanvas-mongodb-stock-market_hu_b81b4d5aaeb10d8f.webp 1400w" src="https://percona.community/blog/2026/09/dbcanvas-mongodb-stock-market_hu_b81b4d5aaeb10d8f.webp" alt=" " loading="lazy"></figure></p>
<p><em>Figure 6: A MongoDB replica set running the stock market simulator with diagnostic tools attached</em></p>
<h2>Leaning on tools other people already built</h2>
<p>DBCanvas can make deployments more useful by integrating tools my colleagues have built for managing, troubleshooting and analyzing database environments. I can add them as part of the deployment workflow and place it alongside the database it is intended to work with.</p>
<ul>
<li><strong><a href="https://github.com/PrzemekMalkowski/mclusteradmin" target="_blank" rel="noopener noreferrer">MClusterAdmin</a></strong>, a MongoDB administration panel created by Przemek Malkowski, runs as its own node alongside a MongoDB deployment. It displays topology and replica-set status, sharding and the balancer, slow queries with explain, indexes, users and roles, all in a browser tab.</li>
</ul>
<p>
<figure><img width="1594" height="943" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/dbcanvas-mclusteradmin_hu_d22d3d37074ba958.webp 768w, https://percona.community/blog/2026/09/dbcanvas-mclusteradmin_hu_84be37c3bcc6da2e.webp 480w, https://percona.community/blog/2026/09/dbcanvas-mclusteradmin_hu_1bb9d6571de1276a.webp 1400w" src="https://percona.community/blog/2026/09/dbcanvas-mclusteradmin_hu_1bb9d6571de1276a.webp" alt=" " loading="lazy"></figure></p>
<p><em>Figure 7: MClusterAdmin displaying the replica-set state for a DBCanvas deployment</em></p>
<ul>
<li><strong><a href="https://github.com/zelmario/Big-hole" target="_blank" rel="noopener noreferrer">Big Hole</a></strong>, an FTDC viewer, developed by Zelmar Michelini that decodes <code>diagnostic.data</code>. Drag in a folder and Big Hole charts every metric it can find. In DBCanvas, collecting the data is just as straightforward: right-click a MongoDB node, open the context menu, and download a compressed archive containing its logs and diagnostic data. Simply decompress the archive to your download directory, then drag the folder into Big Hole to visualize FTDC metrics alongside related log events.</li>
</ul>
<p>
<figure><img width="1595" height="992" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/dbcanvas-big-hole_hu_24f08c57552c1445.webp 768w, https://percona.community/blog/2026/09/dbcanvas-big-hole_hu_c28d66a515f33a6b.webp 480w, https://percona.community/blog/2026/09/dbcanvas-big-hole_hu_7f5878becf0ad507.webp 1400w" src="https://percona.community/blog/2026/09/dbcanvas-big-hole_hu_7f5878becf0ad507.webp" alt=" " loading="lazy"></figure></p>
<p><em>Figure 8: Big Hole visualizing FTDC metrics downloaded from a MongoDB node</em></p>
<p>The same principle applies to deeper diagnostic work. DBCanvas automates the setup around proven tools instead of replacing them.</p>
<p>For example, the Operator Debugger uses Delve to step through Kubernetes operators with breakpoints, call stacks and variables.</p>
<p>
<figure><img width="1671" height="1091" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/dbcanvas-operator-debugger_hu_2224ed56005be686.webp 768w, https://percona.community/blog/2026/09/dbcanvas-operator-debugger_hu_dde7893f2565de3a.webp 480w, https://percona.community/blog/2026/09/dbcanvas-operator-debugger_hu_c10fd70a85f605bc.webp 1400w" src="https://percona.community/blog/2026/09/dbcanvas-operator-debugger_hu_c10fd70a85f605bc.webp" alt=" " loading="lazy"></figure></p>
<p><em>Figure 9: Operator Debugger paused at a breakpoint inside a Percona operator</em></p>
<p>There’s also the Core Dump Analyzer where you can mount a core dump and matching binaries read-only, then inspect it with GDB.</p>
<p>
<figure><img width="1674" height="1042" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/dbcanvas-core-dump-analyzer_hu_cfaed05616452916.webp 768w, https://percona.community/blog/2026/09/dbcanvas-core-dump-analyzer_hu_9fc8ce99e2a68b9a.webp 480w, https://percona.community/blog/2026/09/dbcanvas-core-dump-analyzer_hu_8d2e7ebbf7d41842.webp 1400w" src="https://percona.community/blog/2026/09/dbcanvas-core-dump-analyzer_hu_8d2e7ebbf7d41842.webp" alt=" " loading="lazy"></figure></p>
<p><em>Figure 10: Web-based Core Dump Analyzer with equivalent terminal command for manual troubleshooting</em></p>
<h2>Ease of Use</h2>
<p>While DBCanvas makes it easy to deploy environments but for troubleshooting, you still need to look deeper into the implementation within the nodes. Accessing the deployment via web terminal, regular terminal or web-based Filemanager would be helpful to have command of the deployment.</p>
<p>
<figure><img width="1648" height="1026" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/dbcanvas-file-manager_hu_89e840abe079e171.webp 768w, https://percona.community/blog/2026/09/dbcanvas-file-manager_hu_576d4d6d65f8b06a.webp 480w, https://percona.community/blog/2026/09/dbcanvas-file-manager_hu_37006883d5ec663.webp 1400w" src="https://percona.community/blog/2026/09/dbcanvas-file-manager_hu_37006883d5ec663.webp" alt=" " loading="lazy"></figure></p>
<p><em>Figure 11: The file manager inspecting a MongoDB configuration inside a deployed node</em></p>
<p>The node menu provides the exact Docker exec command for direct access from a regular terminal.</p>
<p>
<figure><img width="1303" height="811" sizes="(max-width: 1303px) 100vw, 1303px" srcset="https://percona.community/blog/2026/09/dbcanvas-docker-exec_hu_82172d498e7a0a82.webp 768w, https://percona.community/blog/2026/09/dbcanvas-docker-exec_hu_931fbaca34648d3c.webp 480w, https://percona.community/blog/2026/09/dbcanvas-docker-exec_hu_1b3260e390025326.webp 1303w" src="https://percona.community/blog/2026/09/dbcanvas-docker-exec_hu_1b3260e390025326.webp" alt=" " loading="lazy"></figure></p>
<p><em>Figure 12: Direct container access from a Docker exec command copied from the node menu</em></p>
<h2>Testing beyond deployment</h2>
<p>DBCanvas also comes with a data generator, a parallel query runner, a benchmark tool (OLTP/OLAP, read-write and read-only), and a packet inspector that decodes MySQL, PostgreSQL, MongoDB and Valkey traffic off the wire.</p>
<p>
<figure><img width="1418" height="1081" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/dbcanvas-data-generator_hu_10ce38511e611d56.webp 768w, https://percona.community/blog/2026/09/dbcanvas-data-generator_hu_8f428b69fc9fef5f.webp 480w, https://percona.community/blog/2026/09/dbcanvas-data-generator_hu_eaf1ffab93205438.webp 1400w" src="https://percona.community/blog/2026/09/dbcanvas-data-generator_hu_eaf1ffab93205438.webp" alt=" " loading="lazy"></figure></p>
<p><em>Figure 13: Data Generator creating sample rows for a deployed database</em></p>
<p>
<figure><img width="1467" height="797" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/dbcanvas-packet-inspector_hu_3ea4936542bd887b.webp 768w, https://percona.community/blog/2026/09/dbcanvas-packet-inspector_hu_62b38388fe6728fa.webp 480w, https://percona.community/blog/2026/09/dbcanvas-packet-inspector_hu_a3fb6877ab50ffc2.webp 1400w" src="https://percona.community/blog/2026/09/dbcanvas-packet-inspector_hu_a3fb6877ab50ffc2.webp" alt=" " loading="lazy"></figure></p>
<p><em>Figure 14: Packet Inspector decoding MongoDB traffic from the lab network</em></p>
<p>Together, these tools help reproduce problems facing a real deployment as I can generate data, apply load, inspect queries and examine network traffic.</p>
<h2>Try it</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-0" aria-label="Copy code to clipboard">
<span class="code-block__copy-default">Copy</span>
<span class="code-block__copy-success" aria-hidden="true">Copied!</span>
</button>
</div>
<div class="code-block__content">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/jaimesicam/dbcanvas.git <span class="o">&amp;&amp;</span> <span class="nb">cd</span> dbcanvas
</span></span><span class="line"><span class="cl">make install</span></span></code></pre></div>
</div>
</div>
<p>That builds the node images and starts DBCanvas at <code>http://localhost:8080</code>. The first run takes a while since it’s building docker images from scratch and learning which software versions are available for each OS.</p>
<p>Three important caveats:</p>
<p>DBCanvas creates disposable labs for previewing, testing and learning. It’s not a production software. It uses default credentials and favors setup speed over production security.</p>
<p>My workstation remains its primary test environment. If you run it elsewhere and encounter a bug or rough edge, please open a GitHub issue.</p>
<p>Some features are still maturing. I chose to bring many capabilities into one project, which means a few areas still need additional refinement and testing. For example, the Core Dump analyzer could benefit from more sophisticated variable extraction as well as the automatic detection of the appropriate operating system and compatible debug libraries to deploy.</p>
<p>If you’ve ever rebuilt the same test cluster for the third time this month, or wished you could hand a colleague a working reproduction instead of a page of setup instructions, try DBCanvas as it might save you some of the time it has saved me.</p>]]></content:encoded>
    <pubDate>Wed, 16 Sep 2026 10:31:00 +0000</pubDate>
    <dc:creator>Percona Community</dc:creator>
    <category>MySQL</category>
    <category>VibeCoding</category>
    <category>Percona</category>
    <category>Ai</category>
    <category>Labs</category>
    <category>Database</category>
    <category>Docker</category>
  </item>

  <item>
    <title>NodeJS MySQL Select Unique</title>
    <guid isPermaLink="false">https://codeforgeek.com/?p=14838</guid>
    <link>https://codeforgeek.com/nodejs-mysql-select-unique/</link>
    <description>I ran SELECT UNIQUE age FROM users on a table with a duplicated age. It returned 22, 17 and 15, the same rows SELECT DISTINCT returns. On MySQL 8 that statement stops before the table is read, because UNIQUE never entered the server’s select grammar, and DISTINCT is the modifier both servers accept. Which unique […]</description>
    <pubDate>Wed, 16 Sep 2026 10:12:31 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Node Tutorials</category>
  </item>

  <item>
    <title>Vector search in MySQL: an early look at HNSW and custom indexes in VillageSQL</title>
    <guid isPermaLink="false">6aa85eaf5b6b4000010bbb7a</guid>
    <link>https://villagesql.com/blog/vector-search-hnsw/</link>
    <description>MySQL 9.x added the VECTOR data type so you can store an embedding. You get a VECTOR column, functions to convert to and from text, and VECTOR_DIM() to ask how wide a vector is.
What you don't get is a way to compare two vectors. The 9.x community server has no distance function and no vector index. MySQL 8.4 has none of this at all. You can't add the missing index yourself either because vanilla MySQL's list of index types is fixed to B-tree, R-tree, hash, and full-text.
That's where VillageSQL comes in. VillageSQL is the innovation platform for MySQL that adds an extension framework (similar to PostgreSQL's extension framework) to enable permissionless innovation. Instead of waiting for a feature to be implemented in a few years in a future version of MySQL, new functionality can be dynamically added to a version of MySQL you run today.
VillageSQL already supports custom functions and custom data types, and we've been working on custom indexes. With custom indexes, an extension can define a whole index type including how it's stored in InnoDB, how it's built as rows arrive, and how it's searched. The server treats the custom index as a first-class index, the same as the built-in index types. The server even plans queries against the custom index and reports it by name.
Vector search is a key feature for AI-era applications, so that's the first use case for custom indexes we are building.
At Percona Live in Amsterdam on September 9, 2026, we demoed an early build of this vector work (not yet merged). Below is the annotated demo.
Start with a server
The demo starts by installing VillageSQL. You copy one line from the website, paste it into a terminal, and you have a prebuilt server.
curl -fsSL https://install.villagesql.com | bash


            
                
                
                
            
            
        That gets you a stable server. Please note the vector work highlighted below runs on an unmerged, development build.
Declaring a vector index
With the server up, the demo installs the vsql-vector extension with an ordinary INSTALL EXTENSION, and then creates one table:
CREATE TABLE demo_vectors (
  id INT PRIMARY KEY,
  embedding SVECTOR(3) NOT NULL,
  INDEX idx_embedding (embedding hnsw_l2) USING EXTENDED(hnsw)
) ENGINE=InnoDB;

USING EXTENDED(hnsw) names a custom index type and SVECTOR is a custom column type. The extension supplies both. The server stores the metadata, plans the query, and hands the work off. Once the table exists, the vectors arrive by plain INSERT.

            
                
                
                
            
            
        Does the server use it?
You can ask the server whether it will really use that index:
EXPLAIN FORMAT=TREE SELECT id FROM demo_vectors
  ORDER BY l2_distance(embedding, '[1.0,2.0,3.0]') LIMIT 3\G

The plan comes back as Custom index distance scan on idx_embedding, which is the server saying it would answer from the HNSW graph instead of reading every row. Running that same SELECT without EXPLAIN in front of it returns the three nearest rows, closest first.

            
                
                
                
            
            
        Fast and correct
The demo then builds two tables that hold the same 20,000 vectors. One table has an HNSW index on the vector column and the other has no index at all, and the same nearest-neighbor query runs against both.

            
                
                
                
            
            
        Across 50 queries each, the indexed table averages 0.19 ms and the full scan 3.82 ms, which is 20x slower. This is synthetic data at 3 dimensions, so read it as a mechanism check rather than a benchmark.
Speed alone does not prove an index is beneficial, because an approximate index can be fast by being wrong. The unindexed table holds the same vectors, so it gives the exact answer to compare against. Here the index returns nine of the true top ten, and the tenth row is the price of an approximate search. ef_search is a dial that allows you to tune accuracy and performance.
Where it lands on real embeddings
We ran ann-benchmarks against fashion-mnist-784 on an 8 vCPU Xeon VM. Below are our initial results. When vector indexes are merged, we’ll publish steps to reproduce these benchmark results.
fashion-mnist-784 — 60,000 vectors, k=10, 8 vCPU Xeon



ef_search
queries/sec
recall@10




50
635
99.68%


100
440
99.85%


200
290
99.92%



These numbers are a snapshot of the work which is still in progress. They come from one early build, and we expect them to move. The shape of the curve is the interesting part. Each step up in ef_search buys recall and costs throughput. Going from 50 to 100 adds 0.17 points of recall and gives up about 30% of the queries per second. Going to 200 adds another 0.07 points and costs about half. You can choose the point on that curve that your application needs.
How it fits together
The server treats the extension's index as a real one. Ask what indexes a table has, and the vector ones come back as HNSW, sitting next to an ordinary BTREE. SHOW CREATE TABLE (from a different example table with two vector columns) returns the full definition:
KEY `idx_title` (`title_vec` `vsql_vector`.`hnsw_cosine`)
  USING EXTENDED(`vsql_vector`.`hnsw`) WITH (`ef_construction` = 64, `m` = 8),
KEY `idx_body` (`body_vec` `vsql_vector`.`hnsw_l1`),
KEY `idx_tag` (`tag`)


            
                
                
                
            
            
        M and ef_construction are the extension's own build knobs. The server stores them without knowing what they mean, and prints them back in DDL you can replay. The vectors themselves sit in InnoDB's own storage, so there's no shadow table and no second write on insert. Columns go up to 3,072 dimensions (for comparison, pgvector supports 2,000 dimensions for an indexed column), and one table can carry several vector indexes, each on its own column with its own distance function.
What's next
Filtered search, deletes, wider version coverage, and comparison benchmarks are all still ahead. We expect to deliver the custom index framework for both 8.4 and 9.7 codebases.
This is an initial build of unreleased work, and we wanted to show it while it's still moving. If you want to read the code in the meantime, the extension is at github.com/villagesql/vsql-vector, and you can get the server from villagesql.com. VillageSQL Server supports MySQL 8.4, 9.7, and Percona Server 8.4.
Please let us know your feedback. You can find us on Discord or on GitHub Issues.</description>
    <content:encoded><![CDATA[<img src="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/images/2026/09/amsterdam.jpg" alt="Vector search in MySQL: an early look at HNSW and custom indexes in VillageSQL"><p>MySQL 9.x added the <code>VECTOR</code> data type so you can store an embedding. You get a <code>VECTOR</code> column, functions to convert to and from text, and <code>VECTOR_DIM()</code> to ask how wide a vector is.</p>
<p>What you don't get is a way to compare two vectors. The 9.x community server has no distance function and no vector index. MySQL 8.4 has none of this at all. You can't add the missing index yourself either because vanilla MySQL's list of index types is fixed to B-tree, R-tree, hash, and full-text.</p>
<p>That's where VillageSQL comes in. VillageSQL is the innovation platform for MySQL that adds an extension framework (similar to PostgreSQL's extension framework) to enable permissionless innovation. Instead of waiting for a feature to be implemented in a few years in a future version of MySQL, new functionality can be dynamically added to a version of MySQL you run today.</p>
<p>VillageSQL already supports custom functions and custom data types, and we've been working on custom indexes. With custom indexes, an extension can define a whole index type including how it's stored in InnoDB, how it's built as rows arrive, and how it's searched. The server treats the custom index as a first-class index, the same as the built-in index types. The server even plans queries against the custom index and reports it by name.</p>
<p>Vector search is a key feature for AI-era applications, so that's the first use case for custom indexes we are building.</p>
<p>At Percona Live in Amsterdam on September 9, 2026, we demoed an early build of this vector work (not yet merged). Below is the annotated demo.</p>
<h2>Start with a server</h2>
<p>The demo starts by installing VillageSQL. You copy one line from the website, paste it into a terminal, and you have a prebuilt server.</p>
<pre><code class="language-shell">curl -fsSL https://install.villagesql.com | bash
</code></pre>
<figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/01-install-villagesql_thumb.jpg" data-kg-custom-thumbnail>
            <div class="kg-video-container">
                <video src="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/01-install-villagesql.mp4" poster="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/01-install-villagesql_thumb.jpg" width="1512" height="950" loop autoplay muted playsinline preload="none" controls></video>
                
                
            </div>
            
        </figure><p>That gets you a stable server. Please note the vector work highlighted below runs on an unmerged, development build.</p>
<h2>Declaring a vector index</h2>
<p>With the server up, the demo installs the <code>vsql-vector</code> extension with an ordinary <code>INSTALL EXTENSION</code>, and then creates one table:</p>
<pre><code class="language-sql">CREATE TABLE demo_vectors (
  id INT PRIMARY KEY,
  embedding SVECTOR(3) NOT NULL,
  INDEX idx_embedding (embedding hnsw_l2) USING EXTENDED(hnsw)
) ENGINE=InnoDB;
</code></pre>
<p><code>USING EXTENDED(hnsw)</code> names a custom index type and <code>SVECTOR</code> is a custom column type. The extension supplies both. The server stores the metadata, plans the query, and hands the work off. Once the table exists, the vectors arrive by plain <code>INSERT</code>.</p>
<figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/02-install-extension-create-insert_thumb.jpg" data-kg-custom-thumbnail>
            <div class="kg-video-container">
                <video src="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/02-install-extension-create-insert.mp4" poster="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/02-install-extension-create-insert_thumb.jpg" width="1512" height="950" loop autoplay muted playsinline preload="none" controls></video>
                
                
            </div>
            
        </figure><h2>Does the server use it?</h2>
<p>You can ask the server whether it will really use that index:</p>
<pre><code class="language-sql">EXPLAIN FORMAT=TREE SELECT id FROM demo_vectors
  ORDER BY l2_distance(embedding, '[1.0,2.0,3.0]') LIMIT 3\G
</code></pre>
<p>The plan comes back as <code>Custom index distance scan on idx_embedding</code>, which is the server saying it would answer from the HNSW graph instead of reading every row. Running that same <code>SELECT</code> without <code>EXPLAIN</code> in front of it returns the three nearest rows, closest first.</p>
<figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/03-explain-and-search_thumb.jpg" data-kg-custom-thumbnail>
            <div class="kg-video-container">
                <video src="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/03-explain-and-search.mp4" poster="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/03-explain-and-search_thumb.jpg" width="1512" height="950" loop autoplay muted playsinline preload="none" controls></video>
                
                
            </div>
            
        </figure><h2>Fast and correct</h2>
<p>The demo then builds two tables that hold the same 20,000 vectors. One table has an HNSW index on the vector column and the other has no index at all, and the same nearest-neighbor query runs against both.</p>
<figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/04-speed-and-recall_thumb.jpg" data-kg-custom-thumbnail>
            <div class="kg-video-container">
                <video src="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/04-speed-and-recall.mp4" poster="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/04-speed-and-recall_thumb.jpg" width="1512" height="950" loop autoplay muted playsinline preload="none" controls></video>
                
                
            </div>
            
        </figure><p>Across 50 queries each, the indexed table averages 0.19 ms and the full scan 3.82 ms, which is 20x slower. This is synthetic data at 3 dimensions, so read it as a mechanism check rather than a benchmark.</p>
<p>Speed alone does not prove an index is beneficial, because an approximate index can be fast by being wrong. The unindexed table holds the same vectors, so it gives the exact answer to compare against. Here the index returns nine of the true top ten, and the tenth row is the price of an approximate search. <code>ef_search</code> is a dial that allows you to tune accuracy and performance.</p>
<h2>Where it lands on real embeddings</h2>
<p>We ran ann-benchmarks against fashion-mnist-784 on an 8 vCPU Xeon VM. Below are our initial results. When vector indexes are merged, we’ll publish steps to reproduce these benchmark results.</p>
<p><em>fashion-mnist-784</em> — 60,000 vectors, k=10, 8 vCPU Xeon</p>
<table>
<thead>
<tr>
<th>ef_search</th>
<th>queries/sec</th>
<th>recall@10</th>
</tr>
</thead>
<tbody>
<tr>
<td>50</td>
<td>635</td>
<td>99.68%</td>
</tr>
<tr>
<td>100</td>
<td>440</td>
<td>99.85%</td>
</tr>
<tr>
<td>200</td>
<td>290</td>
<td>99.92%</td>
</tr>
</tbody>
</table>
<p>These numbers are a snapshot of the work which is still in progress. They come from one early build, and we expect them to move. The shape of the curve is the interesting part. Each step up in <code>ef_search</code> buys recall and costs throughput. Going from 50 to 100 adds 0.17 points of recall and gives up about 30% of the queries per second. Going to 200 adds another 0.07 points and costs about half. You can choose the point on that curve that your application needs.</p>
<h2>How it fits together</h2>
<p>The server treats the extension's index as a real one. Ask what indexes a table has, and the vector ones come back as <code>HNSW</code>, sitting next to an ordinary <code>BTREE</code>. <code>SHOW CREATE TABLE</code> (from a different example table with two vector columns) returns the full definition:</p>
<pre><code class="language-sql">KEY `idx_title` (`title_vec` `vsql_vector`.`hnsw_cosine`)
  USING EXTENDED(`vsql_vector`.`hnsw`) WITH (`ef_construction` = 64, `m` = 8),
KEY `idx_body` (`body_vec` `vsql_vector`.`hnsw_l1`),
KEY `idx_tag` (`tag`)
</code></pre>
<figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/05-index-metadata_thumb.jpg" data-kg-custom-thumbnail>
            <div class="kg-video-container">
                <video src="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/05-index-metadata.mp4" poster="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/media/2026/09/05-index-metadata_thumb.jpg" width="1512" height="950" loop autoplay muted playsinline preload="none" controls></video>
                
                
            </div>
            
        </figure><p><code>M</code> and <code>ef_construction</code> are the extension's own build knobs. The server stores them without knowing what they mean, and prints them back in DDL you can replay. The vectors themselves sit in InnoDB's own storage, so there's no shadow table and no second write on insert. Columns go up to 3,072 dimensions (for comparison, pgvector supports 2,000 dimensions for an indexed column), and one table can carry several vector indexes, each on its own column with its own distance function.</p>
<h2>What's next</h2>
<p>Filtered search, deletes, wider version coverage, and comparison benchmarks are all still ahead. We expect to deliver the custom index framework for both 8.4 and 9.7 codebases.</p>
<p>This is an initial build of unreleased work, and we wanted to show it while it's still moving. If you want to read the code in the meantime, the extension is at <a href="https://github.com/villagesql/vsql-vector">github.com/villagesql/vsql-vector</a>, and you can get the server from <a href="https://villagesql.com/">villagesql.com</a>. VillageSQL Server supports MySQL 8.4, 9.7, and Percona Server 8.4.</p>
<p>Please let us know your feedback. You can find us on <a href="https://discord.gg/KSr6whd3Fr">Discord</a> or on <a href="https://github.com/villagesql/villagesql-server/issues">GitHub Issues</a>.</p>]]></content:encoded>
    <pubDate>Tue, 15 Sep 2026 17:20:06 +0000</pubDate>
    <dc:creator>VillageSQL</dc:creator>
  </item>

  <item>
    <title>Node.js MySQL Insert Record</title>
    <guid isPermaLink="false">https://codeforgeek.com/?p=14731</guid>
    <link>https://codeforgeek.com/nodejs-mysql-insert-record/</link>
    <description>I pointed a Node script at a MySQL table and the connection succeeded. The INSERT failed on a name containing an apostrophe. Pasting values into the SQL string holds until real data arrives, then a name like O’Brien ends the run with a syntax error. Placeholders are the fix, and the driver escapes every value […]</description>
    <pubDate>Tue, 15 Sep 2026 16:07:16 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Node Tutorials</category>
  </item>

  <item>
    <title>NodeJS MySQL Select Record</title>
    <guid isPermaLink="false">https://codeforgeek.com/?p=14798</guid>
    <link>https://codeforgeek.com/nodejs-mysql-select-record/</link>
    <description>RowDataPacket is the name MySQL hands back to Node.js, and it is the line most readers get stuck on. This is the select step end to end, from a running MySQL server to a callback whose rows you can read. What you need to run MySQL SELECT queries in NodeJS Three things, in dependency order. […]</description>
    <pubDate>Tue, 15 Sep 2026 16:02:45 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Node Tutorials</category>
  </item>

  <item>
    <title>Performance improvements in Percona Server 8.4.11-11</title>
    <guid isPermaLink="false">https://www.percona.com/?p=53491</guid>
    <link>https://www.percona.com/blog/performance-improvements-in-percona-server-8-4-11-11/</link>
    <description>Focusing on Percona Server 8.4.11-11
My previous post (Performance Progression of Percona Server for MySQL 8.4) did a brief review of the performance changes in Percona Server for MySQL 8.4 released in 2026. I recommend reading it first to better understand the material in this post.
Version 8.4.11-11 includes patches that deliver significant improvements in performance and scalability.
One of the most impressive illustrations of the performance bump is reflected in the following graph:
Graph 1 – Percona-Server 8.4.X with 8G buffer  [ INTERACTIVE GRAPH ][ TABLE ]

There are two separate improvements in the version 8.4.11-11:

Faster speed for the number of threads smaller than the number of CPU cores. On a host with 40 CPU cores + hyperthreading, which enables simultaneous execution of 80 threads, all versions of the server keep increasing TPS up to 64 threads. However, the version 8.4.11-11 does a noticeably better job in this section.
Prevent abrupt performance degradation for high thread numbers, which is an issue in the versions 8.4.8-8 and 8.4.10-10. Also, a similar degradation is observed in Upstream MySQL 8.4.11.

All code changes improved the performance of the InnoDB buffer pool and the way it handled pages flushing.
 
Why is this important?
InnoDB is the default storage engine in Percona Server for MySQL. Therefore, InnoDB performance influences the overall performance of the database server.
The InnoDB engine has a buffer pool, which is basically a data cache in RAM. Instead of reading from disk for every query, the server keeps the most-used data pages in memory.
Its performance matters because:

RAM is many times faster than disk – the more queries get results from the buffer pool, the faster the database works.
Every query uses the buffer pool. If its internal locks are slow, all queries queue up behind each other.
The buffer pool must constantly free space for new data. So, the queries do not stall because they need to do extra cleanup work themselves.

InnoDB stores data in fixed-size pages (16 KB by default). Tables and indexes are split into pages, and a page is the unit that moves between disk and the buffer pool – it never reads a single row from disk, always the whole page containing it.
To better understand the mechanism responsible for the InnoDB buffer pool handling of pages we need to explain the Least Recently Used (LRU) concept.
LRU is the strategy used to decide which pages should be removed from the buffer pool when space runs low. All pages in memory reside in a list ordered by usage: recently accessed pages stay near the “hot” end, untouched ones drift to the “cold” end, and get evicted first. This keeps frequently used data in fast memory, while rarely used data is written back to disk.
 
The implementation.
The code is available in the GitHub repository for anyone to see:
https://github.com/percona/percona-server/
 
PS-11444: Narrow the LRU mutex scope in buf_page_init_for_read()
(GitHub: 52d87557)
Significant improvement for read-heavy (IO-bound) workloads. Previously, every physical page read held the pool-wide LRU list mutex while doing extra work, including zeroing a 16 KB page frame – so all reads on a buffer pool instance were serialized on one lock. Now the mutex only covers the actual LRU list insertion, and the rest runs under a much finer-grained page-hash latch. As a result, pages could temporarily become visible in the page hash table prior to their insertion into the LRU list, which requires careful handling in the codebase. Physical reads can now proceed in parallel rather than queueing behind one another. It also fixes a related race (PS-9837) and removes LRU-mutex contention from the purge thread.
NOTE: This improvement was possible by analyzing the lock waits in the profiling data, which pointed to the most contended mutex, which was in LRU list.
 
PS-11445: LRU optimization: LRU manager threads are restored
In this multi-patch ticket the LRU threads were restored to their original state and a few other performance improvements were done.
 
Restore per-buffer-pool LRU manager thread
(GitHub: 53bb2f0f)
With this patch each buffer pool instance gets its own background thread that evicts and flushes cold pages to keep free pages available. When this is not done, user queries needing a free page might start  doing cleanup work inline during the query execution, which increases the execution time.
 
Close a shutdown/invalidation race
(GitHub: 37cdd537)
This commit has no direct performance impact.
 
Add innodb_lru_threads sysvar
(GitHub: 1a1185b4)
Adds the ON/OFF switching for the LRU manager through innodb_lru_threads system variable. Also, the patch restructures flush statistics, so LRU threads don’t need to synchronize with each other when updating counters – stats are aggregated by a single coordinator thread.
 
Count evictions as adaptive-sleep progress
(GitHub: c80f16557)
The LRU thread adapts how long it sleeps based on whether it is “making progress”. In the previous implementation only flushing pages counted as progress, so freeing memory by evicting clean pages looked like failure and made the thread back off wrongly. Now evictions count as progress too, so the LRU thread avoids backing off under clean-page workloads.
 
Single-page-flush concurrency cap
(GitHub: 2624d116)
Fixes a low-concurrency throughput drop: when a background LRU batch was already running, a user thread needing a free page always sat waiting for the whole batch to finish. Now the thread first scans for a page it can free immediately, only waits when it truly has to, and never waits twice in one call (avoiding starvation). Adds two monitoring counters for observability.
 
PS-11446: Fix inverted old/young check in LRU scan iterator
(GitHub: dc344fba)
This improvement fixes a bug when the background flusher wasted CPU time re-scanning the same pages over and over because it started from LRU tail each time. After the fix, the LRU scan iterator remembers where it left off and continues from there.
NOTE: This bug existed in MySQL and Percona Server for MySQL for a long time unnoticed. It was discovered only because the LRU code was re-assessed for possible performance improvements.
 
Summary
The overall performance improvements were delivered by: faster physical reads through much less lock contention (PS-11444), background threads keeping free pages ready so queries don’t stall on evictions (PS-11445) and no wasted rescanning in the flusher (PS-11446).
The post Performance improvements in Percona Server 8.4.11-11 appeared first on Percona.</description>
    <content:encoded><![CDATA[<h2><span>Focusing on Percona Server 8.4.11-11</span></h2>
<p><span>My previous post (</span><a href="https://www.percona.com/blog/performance-progression-of-percona-server-for-mysql-8-4/" target="_blank" rel="noopener"><span>Performance Progression of Percona Server for MySQL 8.4</span></a><span>) did a brief review of the performance changes in Percona Server for MySQL 8.4 released in 2026. I recommend reading it first to better understand the material in this post.</span></p>
<p><span>Version 8.4.11-11 includes patches that deliver significant improvements in performance and scalability.</span></p>
<p><span>One of the most impressive illustrations of the performance bump is reflected in the following graph:</span></p>
<p><b>Graph 1 – Percona-Server 8.4.X with 8G buffer </b><span> [ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=8" target="_blank" rel="noopener"><span>INTERACTIVE GRAPH</span></a><span> ][ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=table&amp;mem=8" target="_blank" rel="noopener"><span>TABLE</span></a><span> ]</span><br>
<img decoding="async" class="alignnone size-full wp-image-52471" src="https://www.percona.com/wp-content/uploads/2026/08/graph-8g.png" alt="" width="1013" height="621" srcset="https://www.percona.com/wp-content/uploads/2026/08/graph-8g.png 1013w, https://www.percona.com/wp-content/uploads/2026/08/graph-8g-300x184.png 300w, https://www.percona.com/wp-content/uploads/2026/08/graph-8g-768x471.png 768w" sizes="(max-width: 1013px) 100vw, 1013px"></p>
<p><span>There are two separate improvements in the version 8.4.11-11:</span></p>
<ul>
<li aria-level="1"><span>Faster speed for the number of threads smaller than the number of CPU cores. On a host with 40 CPU cores + hyperthreading, which enables simultaneous execution of 80 threads, all versions of the server keep increasing TPS up to 64 threads. However, the version 8.4.11-11 does a noticeably better job in this section.</span></li>
<li aria-level="1"><span>Prevent abrupt performance degradation for high thread numbers, which is an issue in the versions 8.4.8-8 and 8.4.10-10. Also, a similar degradation is observed in Upstream MySQL 8.4.11.</span></li>
</ul>
<p><span>All code changes improved the performance of the InnoDB buffer pool and the way it handled pages flushing.</span></p>
<p> </p>
<h2><span>Why is this important?</span></h2>
<p><span>InnoDB is the default storage engine in Percona Server for MySQL. Therefore, InnoDB performance influences the overall performance of the database server.</span></p>
<p><span>The InnoDB engine has a buffer pool, which is basically a data cache in RAM. Instead of reading from disk for every query, the server keeps the most-used data pages in memory.</span></p>
<p><span>Its performance matters because:</span></p>
<ul>
<li aria-level="1"><span>RAM is many times faster than disk – the more queries get results from the buffer pool, the faster the database works.</span></li>
<li aria-level="1"><span>Every query uses the buffer pool. If its internal locks are slow, all queries queue up behind each other.</span></li>
<li aria-level="1"><span>The buffer pool must constantly free space for new data. So, the queries do not stall because they need to do extra cleanup work themselves.</span></li>
</ul>
<p><span>InnoDB stores data in fixed-size pages (16 KB by default). Tables and indexes are split into pages, and a page is the unit that moves between disk and the buffer pool – it never reads a single row from disk, always the whole page containing it.</span></p>
<p><span>To better understand the mechanism responsible for the InnoDB buffer pool handling of pages we need to explain the Least Recently Used (LRU) concept.</span></p>
<p><span>LRU is the strategy used to decide which pages should be removed from the buffer pool when space runs low. All pages in memory reside in a list ordered by usage: recently accessed pages stay near the “hot” end, untouched ones drift to the “cold” end, and get evicted first. This keeps frequently used data in fast memory, while rarely used data is written back to disk.</span></p>
<p> </p>
<h2><span>The implementation.</span></h2>
<p><span>The code is available in the GitHub repository for anyone to see:</span></p>
<p><a href="https://github.com/percona/percona-server/" target="_blank" rel="noopener"><span>https://github.com/percona/percona-server/</span></a></p>
<p> </p>
<h4><span>PS-11444: Narrow the LRU mutex scope in buf_page_init_for_read()<br>
(GitHub: </span><a href="https://github.com/percona/percona-server/commit/52d875574dad6df0f9c3beda3245500858fbb1ed" target="_blank" rel="noopener"><span>52d87557</span></a><span>)</span></h4>
<p><span>Significant improvement for read-heavy (IO-bound) workloads. Previously, every physical page read held the pool-wide LRU list mutex while doing extra work, including zeroing a 16 KB page frame – so all reads on a buffer pool instance were serialized on one lock. Now the mutex only covers the actual LRU list insertion, and the rest runs under a much finer-grained page-hash latch. As a result, pages could temporarily become visible in the page hash table prior to their insertion into the LRU list, which requires careful handling in the codebase. Physical reads can now proceed in parallel rather than queueing behind one another. It also fixes a related race (PS-9837) and removes LRU-mutex contention from the purge thread.</span></p>
<p><span>NOTE: This improvement was possible by analyzing the lock waits in the profiling data, which pointed to the most contended mutex, which was in LRU list.</span></p>
<p> </p>
<h4><span>PS-11445: LRU optimization: LRU manager threads are restored</span></h4>
<p><span>In this multi-patch ticket the LRU threads were restored to their original state and a few other performance improvements were done.</span></p>
<p> </p>
<h4><span>Restore per-buffer-pool LRU manager thread</span></h4>
<p><span>(GitHub: </span><a href="https://github.com/percona/percona-server/commit/53bb2f0f8c4e34e9e437f5465299c0c4fd72d34c" target="_blank" rel="noopener"><span>53bb2f0f</span></a><span>)</span></p>
<p><span>With this patch each buffer pool instance gets its own background thread that evicts and flushes cold pages to keep free pages available. When this is not done, user queries needing a free page might start  doing cleanup work inline during the query execution, which increases the execution time.</span></p>
<p> </p>
<h4><span>Close a shutdown/invalidation race</span></h4>
<p><span>(GitHub: </span><a href="https://github.com/percona/percona-server/commit/37cdd537d1364253d2b30363ffedb1c1fa66a4d9" target="_blank" rel="noopener"><span>37cdd537</span></a><span>)</span></p>
<p><span>This commit has no direct performance impact.</span></p>
<p> </p>
<h4><span>Add innodb_lru_threads sysvar</span></h4>
<p><span>(GitHub: </span><a href="https://github.com/percona/percona-server/commit/1a1185b4ca28ff6b6bd8e5f8ae2eef12933c84b9" target="_blank" rel="noopener"><span>1a1185b4</span></a><span>)</span></p>
<p><span>Adds the ON/OFF switching for the LRU manager through innodb_lru_threads system variable. Also, the patch restructures flush statistics, so LRU threads don’t need to synchronize with each other when updating counters – stats are aggregated by a single coordinator thread.</span></p>
<p> </p>
<h4><span>Count evictions as adaptive-sleep progress</span></h4>
<p><span>(GitHub: </span><a href="https://github.com/percona/percona-server/commit/c80f165574e1d24c60a3b5cb5301465ca112688f" target="_blank" rel="noopener"><span>c80f16557</span></a><span>)</span></p>
<p><span>The LRU thread adapts how long it sleeps based on whether it is “making progress”. In the previous implementation only flushing pages counted as progress, so freeing memory by evicting clean pages looked like failure and made the thread back off wrongly. Now evictions count as progress too, so the LRU thread avoids backing off under clean-page workloads.</span></p>
<p> </p>
<h4><span>Single-page-flush concurrency cap</span></h4>
<p><span>(GitHub: </span><a href="https://github.com/percona/percona-server/commit/2624d1168cf1b74fcd5bcc3e9cb32648ab2e39b8" target="_blank" rel="noopener"><span>2624d116</span></a><span>)</span></p>
<p><span>Fixes a low-concurrency throughput drop: when a background LRU batch was already running, a user thread needing a free page always sat waiting for the whole batch to finish. Now the thread first scans for a page it can free immediately, only waits when it truly has to, and never waits twice in one call (avoiding starvation). Adds two monitoring counters for observability.</span></p>
<p> </p>
<h4><span>PS-11446: Fix inverted old/young check in LRU scan iterator</span></h4>
<p><span>(GitHub: </span><a href="https://github.com/percona/percona-server/commit/dc344fba7e3d272ac18e8d6589b37c678f1e1ad5" target="_blank" rel="noopener"><span>dc344fba</span></a><span>)</span></p>
<p><span>This improvement fixes a bug when the background flusher wasted CPU time re-scanning the same pages over and over because it started from LRU tail each time. After the fix, the LRU scan iterator remembers where it left off and continues from there.</span></p>
<p><span>NOTE: This bug existed in MySQL and Percona Server for MySQL for a long time unnoticed. It was discovered only because the LRU code was re-assessed for possible performance improvements.</span></p>
<p> </p>
<h2><span>Summary</span></h2>
<p><span>The overall performance improvements were delivered by: faster physical reads through much less lock contention (PS-11444), background threads keeping free pages ready so queries don’t stall on evictions (PS-11445) and no wasted rescanning in the flusher (PS-11446).</span></p>
<p>The post <a href="https://www.percona.com/blog/performance-improvements-in-percona-server-8-4-11-11/">Performance improvements in Percona Server 8.4.11-11</a> appeared first on <a href="https://www.percona.com/">Percona</a>.</p>]]></content:encoded>
    <pubDate>Tue, 15 Sep 2026 11:52:54 +0000</pubDate>
    <dc:creator>MySQL Performance Blog</dc:creator>
    <category>Benchmarks</category>
    <category>MySQL</category>
    <category>benchmark</category>
    <category>Percona Server for MySQL</category>
  </item>

  <item>
    <title>MySQL INSTANT DDL breaks EXCHANGE PARTITION</title>
    <guid isPermaLink="false">https://kedar.nitty-witty.com/blog/?p=3627</guid>
    <link>https://kedar.nitty-witty.com/blog/mysql-instant-ddl-breaks-exchange-partition?utm_source=rss&amp;amp;utm_medium=rss&amp;amp;utm_campaign=mysql-instant-ddl-breaks-exchange-partition</link>
    <description>MySQL ERROR 1731 'Non matching attribute INSTANT COLUMN(s)' breaks EXCHANGE PARTITION after ALGORITHM=INSTANT. This blog offers reasoning and fixtures.
The post MySQL INSTANT DDL breaks EXCHANGE PARTITION first appeared on Change Is Inevitable.</description>
    <content:encoded><![CDATA[<p>MySQL ERROR 1731 'Non matching attribute INSTANT COLUMN(s)' breaks EXCHANGE PARTITION after ALGORITHM=INSTANT. This blog offers reasoning and fixtures.</p>
The post <a href="https://kedar.nitty-witty.com/blog/mysql-instant-ddl-breaks-exchange-partition">MySQL INSTANT DDL breaks EXCHANGE PARTITION</a> first appeared on <a href="https://kedar.nitty-witty.com/blog">Change Is Inevitable</a>.]]></content:encoded>
    <pubDate>Mon, 14 Sep 2026 13:36:10 +0000</pubDate>
    <dc:creator>Kedar Vaijanapurkar</dc:creator>
    <category>MySQL</category>
    <category>MySQL-Articles</category>
    <category>ALGORITHM=INSTANT</category>
    <category>ER_PARTITION_EXCHANGE_DIFFERENT_OPTION</category>
    <category>exchange partition</category>
    <category>MySQL ERROR 1731</category>
    <category>Non matching attribute INSTANT COLUMN</category>
  </item>

  <item>
    <title>NodeJS MySQL Drop Table</title>
    <guid isPermaLink="false">https://codeforgeek.com/?p=14861</guid>
    <link>https://codeforgeek.com/nodejs-mysql-drop-table/</link>
    <description>DROP TABLE crashed my Node process when I re-ran it without a guard, and the fix turns that crash into a warning. I built every code path below on Node 26.7.0 with mysql 2.18.1 and mysql2 3.24.4 against MariaDB 10.11.14. I captured the terminal output as images so what you run matches what I saw. […]</description>
    <pubDate>Sun, 13 Sep 2026 14:07:59 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Node Tutorials</category>
  </item>

  <item>
    <title>NodeJS MySQL Create Table</title>
    <guid isPermaLink="false">https://codeforgeek.com/?p=14714</guid>
    <link>https://codeforgeek.com/nodejs-mysql-create-table/</link>
    <description>I ran node app.js once and the users table appeared, then I ran it again and MySQL returned Table users already exists. If you copy the Node.js MySQL create table snippet from an older tutorial you expect it to be safe to rerun because the code looks like any other setup script, so the duplicate […]</description>
    <pubDate>Sun, 13 Sep 2026 11:10:23 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Node Tutorials</category>
  </item>

  <item>
    <title>NodeJS MySQL Create Database</title>
    <guid isPermaLink="false">https://codeforgeek.com/?p=14695</guid>
    <link>https://codeforgeek.com/nodejs-mysql-create-database/</link>
    <description>Node.js can create a MySQL database with one query, but the connection that runs it has to be set up without naming a database first. I hit ER_DB_CREATE_EXISTS on the next run because my first draft skipped IF NOT EXISTS, which means the same script blows up the moment you re-run it. The safe path […]</description>
    <pubDate>Sun, 13 Sep 2026 10:08:02 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Node Tutorials</category>
  </item>

  <item>
    <title>MySQL Workbench 26.7で試してほしい5つのこと</title>
    <guid isPermaLink="false">7565d7379cb13c1135863e30474c28ab</guid>
    <link>https://blogs.oracle.com/mysql/five-things-to-try-in-mysql-workbench-26-7-jp</link>
    <description>本記事は、Five Things to Try in MySQL Workbench 26.7の日本語訳です。 MySQL Workbench 26.7では、最新のテクノロジーとMySQL Shellを基盤として構築した、新世代のMySQL Workbenchを導入しました。 Alfredo Kojimaによるブログ「MySQL Workbench 26のご紹介」では、新しいアーキテクチャと今回のリリースで利用できる機能について分かりやすく紹介しています。ここではその内容を繰り返すのではなく、Workbench 26.7をインストールしたら、ぜひ試していただきたい機能をいくつか紹介します。 すぐに確認できるものもあれば、新しいインターフェースを使い込んでいくうちに初めて気付くような機能もあります。 1. Notebookを使う、またはSQL Scriptを使い続ける SQL Notebookでは、クエリ、結果、説明用のテキストを1つのインタラクティブなドキュメントにまとめることで、SQLを扱う新しい方法を提供します。 ただし、Notebookは従来のSQL Script Editorを置き換えるものではなく、もう1つのオプションです。 エディタ上部にある+ボタンを見てみましょう。ここから、NotebookとSQL Scriptのどちらも簡単に作成でき、作業内容に最適なスタイルを選択できます。 試行錯誤しながら進める作業、トラブルシューティング、デモ、記録として残したい分析にはNotebookが非常に便利です。単純にSQLを記述して実行したい場合には、使い慣れたScriptも引き続き利用できます。 ヒント: どちらか一方を選ぶ必要はありません。私は、作業内容に応じてNotebooksとScriptsを切り替えて使うのが便利だと思います。 SQL Notebooks SQL Script Editor 2. 接続一覧だけではないClient Connectionsを使ってみる Client Connections画面は、新しいWorkbenchで特に試していただきたい機能の1つです。 これは単なるアクティブなセッションの一覧ではありません。 接続を選択すると、以下の5つのビューから詳細を確認できます。 Details Summary Locks Statement History これらのビューを組み合わせることで、個々の接続で何が実行されているのかを、より詳しい情報を得ることができます。 セッションに関する情報を確認したり、実行中のステートメントを調べたり、ロックを確認したり、最近実行されたステートメントの履歴を確認したりできます。 そして状況を把握したら、同じインターフェースから直接操作することもできます。現在実行中のステートメントを終了したり、必要に応じて接続自体を終了したりできます。 ヒント: アプリケーションが停止しているように見える場合は、まず接続のSummaryを確認し、次にStatementとLocksを見てみましょう。特定のステートメントが原因なら、接続全体を切断せずにそのステートメントだけを停止できることがあります。 3. […]</description>
    <pubDate>Sat, 12 Sep 2026 17:40:00 +0000</pubDate>
    <dc:creator>Oracle MySQL Group</dc:creator>
  </item>

  <item>
    <title>NodeJS MySQL Create Connection</title>
    <guid isPermaLink="false">https://codeforgeek.com/?p=14670</guid>
    <link>https://codeforgeek.com/nodejs-mysql-create-connection/</link>
    <description>I rebuilt this MySQL connection from scratch on Node 26 with mysql2 3.24.4 because the old page still taught the unmaintained mysql package. It left every error to a throw that crashes your process. I ran each snippet below against a local MariaDB 10.11 on 3306, so every ER_ACCESS_DENIED_ERROR you see came from a run […]</description>
    <pubDate>Sat, 12 Sep 2026 14:05:55 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Node Tutorials</category>
  </item>

  <item>
    <title>Node.js and MySQL Connection Pool Example</title>
    <guid isPermaLink="false">https://codeforgeek.com/?p=7440</guid>
    <link>https://codeforgeek.com/node-mysql-connection-pool-example/</link>
    <description>I threw a per-request MySQL connection into a small Node API and watched it limp under concurrent load, so I rebuilt it with a mysql2 pool and kept the receipts. I created the pool with createPool on Node v26.7.0 and mysql2 3.24.4, ran pool.query and pool.getConnection against a local host, and got ER_ACCESS_DENIED_NO_PASSWORD_ERROR back because […]</description>
    <pubDate>Sat, 12 Sep 2026 11:06:14 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Node Tutorials</category>
  </item>

  <item>
    <title>Five Things to Try in MySQL Workbench 26.7</title>
    <guid isPermaLink="false">9083bda053895708105e2ae1299708f9</guid>
    <link>https://blogs.oracle.com/mysql/five-things-to-try-in-mysql-workbench-26-7</link>
    <description>With MySQL Workbench 26.7, we introduced a new generation of MySQL Workbench, built on updated technology and on the MySQL Shell foundation. Alfredo Kojima’s post, Introducing MySQL Workbench 26, provides a great overview of the new architecture and the capabilities available in this release. Rather than repeat that overview, I wanted to share a few things […]</description>
    <pubDate>Fri, 11 Sep 2026 17:40:13 +0000</pubDate>
    <dc:creator>Oracle MySQL Group</dc:creator>
    <category>mysql</category>
    <category>MySQL Enterprise Backup</category>
    <category>MySQL GUI Plugins</category>
    <category>MySQL Workbench</category>
    <category>MySQL Workbench 26.7</category>
    <category>SQL Notebooks</category>
    <category>Visual Explain</category>
  </item>

  <item>
    <title>Synchronize MySQL and IndexedDB in an Offline-First App</title>
    <guid isPermaLink="false">http://codeforgeek.com/?p=748</guid>
    <link>https://codeforgeek.com/sync-app-mysql-indexeddb/</link>
    <description>When you build an offline-first app, the question is what happens to a change while there is no network. The answer is a queue that writes to IndexedDB first, pushes to MySQL, and clears the local copy only after the server confirms. The library versions behind that run are Express 5.2.1 and mysql2 3.24.4, both […]</description>
    <pubDate>Fri, 11 Sep 2026 11:20:30 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>JavaScript</category>
    <category>Node Tutorials</category>
    <category>Tutorials</category>
  </item>

  <item>
    <title>MySQL Community Meetups Fall 2026</title>
    <guid isPermaLink="false">7ae026344ba0c21c34ecb629d6d3a430</guid>
    <link>https://blogs.oracle.com/mysql/mysql-community-meetups-fall-2026</link>
    <description>In our recent Where can you find MySQL from August through October 2026?, we shared a broad view of MySQL conferences, community activities, and user group meetups. Since then, community gatherings in Denver, Taipei, and Nashville have brought MySQL users, developers, and contributors together for technical discussion and local connection. We are continuing that momentum […]</description>
    <pubDate>Fri, 11 Sep 2026 10:22:42 +0000</pubDate>
    <dc:creator>Oracle MySQL Group</dc:creator>
    <category>MySQL</category>
    <category>MySQL Community</category>
  </item>

  <item>
    <title>Introducing MySQL Workbench 26</title>
    <guid isPermaLink="false">d6444938e4c0e0c9de8c16b06d2de054</guid>
    <link>https://blogs.oracle.com/mysql/introducing-mysql-workbench-26</link>
    <description>The MySQL team at Oracle is excited to announce MySQL Workbench 26.7, the first of a new generation of the GUI administration and development tool for MySQL. A New Workbench MySQL Workbench 8 has served the community well, but it’s now old and has reached end of life. Its architecture, built on C++ and platform […]</description>
    <pubDate>Thu, 10 Sep 2026 10:20:35 +0000</pubDate>
    <dc:creator>Oracle MySQL Group</dc:creator>
    <category>MySQL</category>
    <category>MySQL Community</category>
    <category>MySQL Enterprise</category>
    <category>tools</category>
    <category>workbench</category>
  </item>

  <item>
    <title>How to setup and use Percona Binlog Server (PBS)</title>
    <guid isPermaLink="false">https://kedar.nitty-witty.com/blog/?p=3619</guid>
    <link>https://kedar.nitty-witty.com/blog/how-to-setup-and-use-percona-binlog-server-pbs?utm_source=rss&amp;amp;utm_medium=rss&amp;amp;utm_campaign=how-to-setup-and-use-percona-binlog-server-pbs</link>
    <description>Earlier last week I was working with Percona Binlog Server, Percona’s tool for archiving the MySQL binary log (binlog) outside of a traditional replica and exploring what it can do.…
The post How to setup and use Percona Binlog Server (PBS) first appeared on Change Is Inevitable.</description>
    <content:encoded><![CDATA[<p>Earlier last week I was working with Percona Binlog Server, Percona’s tool for archiving the MySQL binary log (binlog) outside of a traditional replica and exploring what it can do.…</p>
The post <a href="https://kedar.nitty-witty.com/blog/how-to-setup-and-use-percona-binlog-server-pbs">How to setup and use Percona Binlog Server (PBS)</a> first appeared on <a href="https://kedar.nitty-witty.com/blog">Change Is Inevitable</a>.]]></content:encoded>
    <pubDate>Thu, 10 Sep 2026 08:14:25 +0000</pubDate>
    <dc:creator>Kedar Vaijanapurkar</dc:creator>
    <category>MySQL</category>
    <category>MySQL tools</category>
    <category>backup</category>
    <category>binary log server</category>
    <category>binlog</category>
    <category>Binlog Server</category>
    <category>mysql binlog server</category>
    <category>PBS</category>
    <category>Percona Binlog Server</category>
    <category>point in time recovery</category>
  </item>

  <item>
    <title>Enabling TLS in PXC without Downtime</title>
    <guid isPermaLink="false">https://www.percona.com/?p=53258</guid>
    <link>https://www.percona.com/blog/enabling-tls-in-pxc-without-downtime/</link>
    <description>Starting with Percona XtraDB Cluster (PXC) 8.0, replication traffic encryption is enabled by default. That said, it’s common to find clusters running without TLS that suddenly need it: a new compliance requirement, an audit finding, a network segment that is no longer considered trusted. 
PXC has a variable for exactly that case, pxc-encrypt-cluster-traffic, which handles SSL encryption for inter-node traffic, including State Snapshot Transfer (SST), Incremental State Transfer (IST), and the group communication the nodes use for replication.
The variable is not dynamic, and turning it on normally costs a full cluster restart since the node that encrypts traffic listens on ssl:// while its peers are still on tcp://. If a node joins the cluster with TLS enabled while remaining nodes don’t, the restarting node fails to reach out to the other peers with the following error:2026-09-02T02:16:02.359992Z 0 [Note] [MY-000000] [Galera] Failed to establish connection: wrong version numberThis is a common OpenSSL error seen when a client sends encrypted TLS traffic to a server that expects unencrypted plaintext.
Starting with PXC 8.0.28 and in all PXC 8.4 versions, there is a way to work around the full cluster restart, which relies on the Galera socket.dynamic option. In this post I’ll go through how this option works and how it can be used to enable TLS without downtime.
The socket.dynamic Option
This provider option lets a node establish communication with other peers using both encrypted and plaintext connections. The instance will initially open an encrypted connection, and if it fails since the other nodes are not using TLS, it will retry to establish an unencrypted connection.
The Joiner error log will show the following sequence:2026-09-02T02:16:43.321209Z 0 [Note] [MY-000000] [Galera] Failed to establish connection: wrong version number
2026-09-02T02:16:43.321521Z 0 [Note] [MY-000000] [Galera] (565f0a92-8283, 'tcp://0.0.0.0:4567') connection established to 1c72fb64-ac57 tcp://172.18.0.3:4567
2026-09-02T02:16:43.321566Z 0 [Note] [MY-000000] [Galera] (565f0a92-8283, 'tcp://0.0.0.0:4567') connection established to 2001e9dd-a09a tcp://172.18.0.4:4567The first line is the same error we saw previously: our node initiated a TLS handshake, but the peer responded with something that was not TLS. Without socket.dynamic, the node would stop at that point and never join, but with it, it retries (without encryption) and is able to establish a connection.
This Galera option can’t be changed at runtime, so you need to restart the node to enable it.
The Procedure
In this procedure, we consider a typical PXC architecture with 3 nodes. Enabling encryption requires two rolling restarts: the first turns TLS on while still allowing plaintext connections, and the second removes that option and forces only TLS connections.

 Distribute the same certificate files across all nodes:

Whether the certificates are auto-generated by MySQL or issued by us, every member of the cluster has to use the same key and certificate files. It’s recommended to keep the certificates outside the data directory for security and configuration clarity, and to keep them clear of SST since a joiner wipes its data directory before a full state transfer, and only files matching the [sst] cpat pattern survive. That default covers *.pem, so the MySQL-generated names are safe, but a .crt or .key left in the data directory is deleted.

 Update the my.cnf on every node:

– pxc-encrypt-cluster-traffic=ON, this switch adds TLS for group communication, IST, and SST.
– socket.dynamic=YES option inside the wsrep_provider_options variable, so each node can speak both protocols while the rollout is in progress.
– The ssl-* variables, in case we’re setting the certificates outside the default path, which is the data directory.
– An [sst] section with the encrypt=4 option and the ssl-* variables. Since pxc-encrypt-cluster-traffic enforces SST channel to encrypt=4, this is a safety measure for those that did not yet performed the change, so donor and joiner can communicate over TLS while the change has not been applied in all nodes. Even though nothing has been restarted, the SST script re-reads my.cnf on every transfer, so as soon as the file is in place the donors encrypt SST whether or not they have been restarted.
The my.cnf file shows the following values:[mysqld]
pxc-encrypt-cluster-traffic=ON
wsrep_provider_options=&quot;socket.dynamic=YES&quot;
ssl-ca=/etc/mysql/certs/ca.pem
ssl-cert=/etc/mysql/certs/server-cert.pem
ssl-key=/etc/mysql/certs/server-key.pem

[sst]
encrypt=4
ssl-ca=/etc/mysql/certs/ca.pem
ssl-cert=/etc/mysql/certs/server-cert.pem
ssl-key=/etc/mysql/certs/server-key.pem

 Restart node #1 and node #2, one at a time:

Wait for each node to rejoin and report Synced before moving to the next. A restarted node will try TLS first and then fall back.
You can verify the change was correctly applied by running the following query. Since the SSL settings are in the wsrep_provider_options variable, which is a long string of key/value pairs, we pull out only the ones we are interested in:mysql&amp;gt; SELECT
    -&amp;gt; REGEXP_SUBSTR(@@wsrep_provider_options, 'socket\\.dynamic = [^;]+')  AS socket_dynamic,
    -&amp;gt; REGEXP_SUBSTR(@@wsrep_provider_options, 'socket\\.ssl = [^;]+')      AS socket_ssl,
    -&amp;gt; REGEXP_SUBSTR(@@wsrep_provider_options, 'socket\\.ssl_ca = [^;]+')   AS socket_ssl_ca,
    -&amp;gt; REGEXP_SUBSTR(@@wsrep_provider_options, 'socket\\.ssl_cert = [^;]+') AS socket_ssl_cert,
    -&amp;gt; REGEXP_SUBSTR(@@wsrep_provider_options, 'socket\\.ssl_key = [^;]+')  AS socket_ssl_key\G
*************************** 1. row ***************************
 socket_dynamic: socket.dynamic = YES
     socket_ssl: socket.ssl = YES
  socket_ssl_ca: socket.ssl_ca = /etc/mysql/certs/ca.pem
socket_ssl_cert: socket.ssl_cert = /etc/mysql/certs/server-cert.pem
 socket_ssl_key: socket.ssl_key = /etc/mysql/certs/server-key.pem
1 row in set (0.00 sec)The provider option socket.ssl and ssl certificates are passed down from MySQL to the provider because pxc-encrypt-cluster-traffic is on.
That confirms the settings reached the provider. That said, the replication traffic is not encrypted yet.
We can check this by using tcpdump on node #1 to listen on the replication port (4567) packets coming from node #2:$ tcpdump -i any -nn -A -s0 &quot;tcp port 4567 and host node2&quot; -c 2000 | grep -a table_testOn node #2 we create the table_test that will be replicated to the other nodes:mysql&amp;gt; create table test.table_test (c1 integer primary key);
Query OK, 0 rows affected (0.10 sec)Since in PXC DDLs are replicated as a statement, tcpdump on node #1 shows the create table command in plaintext:...x..x.j.....y............................ ..E......std............test.create table test.table_test (c1 integer primary key).......

 Restart the third node with socket.dynamic=NO:

Node #3 does not need the socket.dynamic option since the other two nodes are already capable of talking either TLS or plaintext.
We modify the my.cnf option file as below:[mysqld]
wsrep_provider_options=&quot;socket.dynamic=NO&quot;And restart the node. This node will start and request only TLS from the other nodes, which they can respond to, since they already have all the changes in place.
The node will show reaching out to cluster peers over ssl://2026-09-02T02:19:03.640009Z 0 [Note] [MY-000000] [Galera] (aac049fc-a401, 'ssl://0.0.0.0:4567') connection established to 8d5f826f-8dd1 ssl://172.18.0.3:4567
2026-09-02T02:19:03.640410Z 0 [Note] [MY-000000] [Galera] (aac049fc-a401, 'ssl://0.0.0.0:4567') connection established to 565f0a92-8283 ssl://172.18.0.2:4567

 Restart node #1 and node #2, turning off the socket.dynamic option:

In order to disallow the non-encrypted option and to establish a TLS connection with the other nodes, nodes #1 and #2 should be restarted.
Similar to the previous step, when restarting, the nodes will establish communication on TLS.
Node #12026-09-02T02:20:12.565801Z 0 [Note] [MY-000000] [Galera] (d3d58399-9ea2, 'ssl://0.0.0.0:4567') connection established to aac049fc-a401 ssl://172.18.0.4:4567
2026-09-02T02:20:12.565855Z 0 [Note] [MY-000000] [Galera] (d3d58399-9ea2, 'ssl://0.0.0.0:4567') connection established to 8d5f826f-8dd1 ssl://172.18.0.3:4567Node #22026-09-02T02:21:20.911318Z 0 [Note] [MY-000000] [Galera] (fc923848-a6d1, 'ssl://0.0.0.0:4567') connection established to d3d58399-9ea2 ssl://172.18.0.2:4567
2026-09-02T02:21:20.911339Z 0 [Note] [MY-000000] [Galera] (fc923848-a6d1, 'ssl://0.0.0.0:4567') connection established to aac049fc-a401 ssl://172.18.0.4:4567To confirm the replication traffic is encrypted, we can perform the same check as in step 3, by running tcpdump on node #1 and listening on the replication port (4567) for packets coming from node #2:$ tcpdump -i any -nn -A -s0 &quot;tcp port 4567 and host node2&quot; -c 2000 | grep -a table_testOn node #2 we drop the table_test previously created:mysql&amp;gt; drop table test.table_test;
Query OK, 0 rows affected (0.04 sec)tcpdump does not show a matching pattern since the replication traffic is now encrypted.
Disabling TLS
We can use the same mechanism to disable TLS for whatever reason. As with enabling it, this takes two rolling restarts. Starting from a cluster where every node has pxc-encrypt-cluster-traffic=ON we perform the following steps:
– Restart nodes #1 and #2 with socket.dynamic=YES, keeping pxc-encrypt-cluster-traffic=ON, so they accept plaintext connections again. 
– Restart node #3 with pxc-encrypt-cluster-traffic=OFF, it will come up in plaintext only, and the other two nodes will accept it because they are back to accepting both protocols.
– Restart nodes #1 and #2 with pxc-encrypt-cluster-traffic=OFF and without socket.dynamic, which leaves the whole cluster in plaintext.
– Finally, remove from the sst section the encrypt and SSL related variables.
Conclusion
Enabling TLS for inter-node traffic can be done without stopping the whole cluster. The socket.dynamic option lets a node accept both encrypted and plaintext connections during the transition, so the change can be rolled out one node at a time. Please note we’ll need at least two rolling restarts to achieve that the cluster fully communicates via TLS.
The post Enabling TLS in PXC without Downtime appeared first on Percona.</description>
    <content:encoded><![CDATA[<p><span>Starting with Percona XtraDB Cluster (PXC) 8.0, replication traffic encryption is enabled by default. That said, it’s common to find clusters running without TLS that suddenly need it: a new compliance requirement, an audit finding, a network segment that is no longer considered trusted. </span></p>
<p><span>PXC has a variable for exactly that case, pxc-encrypt-cluster-traffic, which handles SSL encryption for inter-node traffic, including State Snapshot Transfer (SST), Incremental State Transfer (IST), and the group communication the nodes use for replication.</span></p>
<p><span>The variable is not dynamic, and turning it on normally costs a full cluster restart since the node that encrypts traffic listens on ssl:// while its peers are still on tcp://. If a node joins the cluster with TLS enabled while remaining nodes don’t, the restarting node fails to reach out to the other peers with the following error:</span></p><pre class="urvanov-syntax-highlighter-plain-tag">2026-09-02T02:16:02.359992Z 0 [Note] [MY-000000] [Galera] Failed to establish connection: wrong version number</pre><p><span>This is a common OpenSSL error seen when a client sends encrypted TLS traffic to a server that expects unencrypted plaintext.</span></p>
<p><span>Starting with PXC 8.0.28 and in all PXC 8.4 versions, there is a way to work around the full cluster restart, which relies on the Galera socket.dynamic option. In this post I’ll go through how this option works and how it can be used to enable TLS without downtime.</span></p>
<h3><span>The socket.dynamic Option</span></h3>
<p><span>This provider option lets a node establish communication with other peers using both encrypted and plaintext connections. The instance will initially open an encrypted connection, and if it fails since the other nodes are not using TLS, it will retry to establish an unencrypted connection.</span></p>
<p><span>The Joiner error log will show the following sequence:</span></p><pre class="urvanov-syntax-highlighter-plain-tag">2026-09-02T02:16:43.321209Z 0 [Note] [MY-000000] [Galera] Failed to establish connection: wrong version number
2026-09-02T02:16:43.321521Z 0 [Note] [MY-000000] [Galera] (565f0a92-8283, 'tcp://0.0.0.0:4567') connection established to 1c72fb64-ac57 tcp://172.18.0.3:4567
2026-09-02T02:16:43.321566Z 0 [Note] [MY-000000] [Galera] (565f0a92-8283, 'tcp://0.0.0.0:4567') connection established to 2001e9dd-a09a tcp://172.18.0.4:4567</pre><p><span>The first line is the same error we saw previously: our node initiated a TLS handshake, but the peer responded with something that was not TLS. Without socket.dynamic, the node would stop at that point and never join, but with it, it retries (without encryption) and is able to establish a connection.</span></p>
<p><span>This Galera option can’t be changed at runtime, so you need to restart the node to enable it.</span></p>
<h3><span>The Procedure</span></h3>
<p><span>In this procedure, we consider a typical PXC architecture with 3 nodes. Enabling encryption requires two rolling restarts: the first turns TLS on while still allowing plaintext connections, and the second removes that option and forces only TLS connections.</span></p>
<ol>
<li><span> Distribute the same certificate files across all nodes:</span></li>
</ol>
<p><span>Whether the certificates are auto-generated by MySQL or issued by us, every member of the cluster has to use the same key and certificate files. It’s recommended to keep the certificates outside the data directory for security and configuration clarity, and to keep them clear of SST since a joiner wipes its data directory before a full state transfer, and only files matching the [sst] cpat pattern survive. That default covers *.pem, so the MySQL-generated names are safe, but a .crt or .key left in the data directory is deleted.</span></p>
<ol start="2">
<li><span> Update the my.cnf on every node:</span></li>
</ol>
<p><span>– pxc-encrypt-cluster-traffic=ON, this switch adds TLS for group communication, IST, and SST.</span></p>
<p><span>– socket.dynamic=YES option inside the wsrep_provider_options variable, so each node can speak both protocols while the rollout is in progress.</span></p>
<p><span>– The ssl-* variables, in case we’re setting the certificates outside the default path, which is the data directory.</span></p>
<p><span>– An [sst] section with the encrypt=4 option and the ssl-* variables. Since pxc-encrypt-cluster-traffic enforces SST channel to encrypt=4, this is a safety measure for those that did not yet performed the change, so donor and joiner can communicate over TLS while the change has not been applied in all nodes. Even though nothing has been restarted, the SST script re-reads my.cnf on every transfer, so as soon as the file is in place the donors encrypt SST whether or not they have been restarted.</span></p>
<p><span>The my.cnf file shows the following values:</span></p><pre class="urvanov-syntax-highlighter-plain-tag">[mysqld]
pxc-encrypt-cluster-traffic=ON
wsrep_provider_options="socket.dynamic=YES"
ssl-ca=/etc/mysql/certs/ca.pem
ssl-cert=/etc/mysql/certs/server-cert.pem
ssl-key=/etc/mysql/certs/server-key.pem

[sst]
encrypt=4
ssl-ca=/etc/mysql/certs/ca.pem
ssl-cert=/etc/mysql/certs/server-cert.pem
ssl-key=/etc/mysql/certs/server-key.pem</pre><p></p>
<ol start="3">
<li><span> Restart node #1 and node #2, one at a time:</span></li>
</ol>
<p><span>Wait for each node to rejoin and report Synced before moving to the next. A restarted node will try TLS first and then fall back.</span></p>
<p><span>You can verify the change was correctly applied by running the following query. Since the SSL settings are in the wsrep_provider_options variable, which is a long string of key/value pairs, we pull out only the ones we are interested in:</span></p><pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; SELECT
    -&gt; REGEXP_SUBSTR(@@wsrep_provider_options, 'socket\\.dynamic = [^;]+')  AS socket_dynamic,
    -&gt; REGEXP_SUBSTR(@@wsrep_provider_options, 'socket\\.ssl = [^;]+')      AS socket_ssl,
    -&gt; REGEXP_SUBSTR(@@wsrep_provider_options, 'socket\\.ssl_ca = [^;]+')   AS socket_ssl_ca,
    -&gt; REGEXP_SUBSTR(@@wsrep_provider_options, 'socket\\.ssl_cert = [^;]+') AS socket_ssl_cert,
    -&gt; REGEXP_SUBSTR(@@wsrep_provider_options, 'socket\\.ssl_key = [^;]+')  AS socket_ssl_key\G
*************************** 1. row ***************************
 socket_dynamic: socket.dynamic = YES
     socket_ssl: socket.ssl = YES
  socket_ssl_ca: socket.ssl_ca = /etc/mysql/certs/ca.pem
socket_ssl_cert: socket.ssl_cert = /etc/mysql/certs/server-cert.pem
 socket_ssl_key: socket.ssl_key = /etc/mysql/certs/server-key.pem
1 row in set (0.00 sec)</pre><p><span>The provider option socket.ssl and ssl certificates are passed down from MySQL to the provider because pxc-encrypt-cluster-traffic is on.</span></p>
<p><span>That confirms the settings reached the provider. That said, the replication traffic is not encrypted yet.</span></p>
<p><span>We can check this by using tcpdump on node #1 to listen on the replication port (4567) packets coming from node #2:</span></p><pre class="urvanov-syntax-highlighter-plain-tag">$ tcpdump -i any -nn -A -s0 "tcp port 4567 and host node2" -c 2000 | grep -a table_test</pre><p><span>On node #2 we create the table_test that will be replicated to the other nodes:</span></p><pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; create table test.table_test (c1 integer primary key);
Query OK, 0 rows affected (0.10 sec)</pre><p><span>Since in PXC DDLs are replicated as a statement, tcpdump on node #1 shows the create table command in plaintext:</span></p><pre class="urvanov-syntax-highlighter-plain-tag">...x..x.j.....y............................ ..E......std............test.create table test.table_test (c1 integer primary key).......</pre><p></p>
<ol start="4">
<li><span> Restart the third node with socket.dynamic=NO:</span></li>
</ol>
<p><span>Node #3 does not need the socket.dynamic option since the other two nodes are already capable of talking either TLS or plaintext.</span></p>
<p><span>We modify the my.cnf option file as below:</span></p><pre class="urvanov-syntax-highlighter-plain-tag">[mysqld]
wsrep_provider_options="socket.dynamic=NO"</pre><p><span>And restart the node. This node will start and request only TLS from the other nodes, which they can respond to, since they already have all the changes in place.</span></p>
<p><span>The node will show reaching out to cluster peers over ssl://</span></p><pre class="urvanov-syntax-highlighter-plain-tag">2026-09-02T02:19:03.640009Z 0 [Note] [MY-000000] [Galera] (aac049fc-a401, 'ssl://0.0.0.0:4567') connection established to 8d5f826f-8dd1 ssl://172.18.0.3:4567
2026-09-02T02:19:03.640410Z 0 [Note] [MY-000000] [Galera] (aac049fc-a401, 'ssl://0.0.0.0:4567') connection established to 565f0a92-8283 ssl://172.18.0.2:4567</pre><p></p>
<ol start="5">
<li><span> Restart node #1 and node #2, turning off the socket.dynamic option:</span></li>
</ol>
<p><span>In order to disallow the non-encrypted option and to establish a TLS connection with the other nodes, nodes #1 and #2 should be restarted.</span></p>
<p><span>Similar to the previous step, when restarting, the nodes will establish communication on TLS.</span></p>
<p><span>Node #1</span></p><pre class="urvanov-syntax-highlighter-plain-tag">2026-09-02T02:20:12.565801Z 0 [Note] [MY-000000] [Galera] (d3d58399-9ea2, 'ssl://0.0.0.0:4567') connection established to aac049fc-a401 ssl://172.18.0.4:4567
2026-09-02T02:20:12.565855Z 0 [Note] [MY-000000] [Galera] (d3d58399-9ea2, 'ssl://0.0.0.0:4567') connection established to 8d5f826f-8dd1 ssl://172.18.0.3:4567</pre><p><span>Node #2</span></p><pre class="urvanov-syntax-highlighter-plain-tag">2026-09-02T02:21:20.911318Z 0 [Note] [MY-000000] [Galera] (fc923848-a6d1, 'ssl://0.0.0.0:4567') connection established to d3d58399-9ea2 ssl://172.18.0.2:4567
2026-09-02T02:21:20.911339Z 0 [Note] [MY-000000] [Galera] (fc923848-a6d1, 'ssl://0.0.0.0:4567') connection established to aac049fc-a401 ssl://172.18.0.4:4567</pre><p><span>To confirm the replication traffic is encrypted, we can perform the same check as in step 3, by running tcpdump on node #1 and listening on the replication port (4567) for packets coming from node #2:</span></p><pre class="urvanov-syntax-highlighter-plain-tag">$ tcpdump -i any -nn -A -s0 "tcp port 4567 and host node2" -c 2000 | grep -a table_test</pre><p><span>On node #2 we drop the table_test previously created:</span></p><pre class="urvanov-syntax-highlighter-plain-tag">mysql&gt; drop table test.table_test;
Query OK, 0 rows affected (0.04 sec)</pre><p><span>tcpdump does not show a matching pattern since the replication traffic is now encrypted.</span></p>
<h3><span>Disabling TLS</span></h3>
<p><span>We can use the same mechanism to disable TLS for whatever reason. As with enabling it, this takes two rolling restarts. Starting from a cluster where every node has pxc-encrypt-cluster-traffic=ON we perform the following steps:</span></p>
<p><span>– Restart nodes #1 and #2 with socket.dynamic=YES, keeping pxc-encrypt-cluster-traffic=ON, so they accept plaintext connections again. </span></p>
<p><span>– Restart node #3 with pxc-encrypt-cluster-traffic=OFF, it will come up in plaintext only, and the other two nodes will accept it because they are back to accepting both protocols.</span></p>
<p><span>– Restart nodes #1 and #2 with pxc-encrypt-cluster-traffic=OFF and without socket.dynamic, which leaves the whole cluster in plaintext.</span></p>
<p><span>– Finally, remove from the sst section the encrypt and SSL related variables.</span></p>
<h3><span>Conclusion</span></h3>
<p><span>Enabling TLS for inter-node traffic can be done without stopping the whole cluster. The socket.dynamic option lets a node accept both encrypted and plaintext connections during the transition, so the change can be rolled out one node at a time. Please note we’ll need at least two rolling restarts to achieve that the cluster fully communicates via TLS.</span></p>
<p>The post <a href="https://www.percona.com/blog/enabling-tls-in-pxc-without-downtime/">Enabling TLS in PXC without Downtime</a> appeared first on <a href="https://www.percona.com/">Percona</a>.</p>]]></content:encoded>
    <pubDate>Wed, 09 Sep 2026 23:32:15 +0000</pubDate>
    <dc:creator>MySQL Performance Blog</dc:creator>
    <category>Insight for DBAs</category>
    <category>MySQL</category>
    <category>Open Source</category>
    <category>Uncategorized</category>
    <category>XtraDB Cluster (PXC)</category>
    <category>galera</category>
    <category>High Availability</category>
    <category>pxc</category>
  </item>

  <item>
    <title>Enabling OCI Log Analytics for MySQL HeatWave – Now available for MySQL 8.4</title>
    <guid isPermaLink="false">7705f8d67931f51cccdef357c1b9081b</guid>
    <link>https://blogs.oracle.com/mysql/enabling-oci-log-analytics-for-mysql-heatwave-now-available-for-mysql-8-4</link>
    <description>MySQL HeatWave telemetry is the built-in capability for exporting selected MySQL server logs to an observability destination. When telemetry is configured with OCI Log Analytics, it export MySQL log natively for search and analysis – supporting security investigations, database and operational errors troubleshooting, identification of costly SQL and performance bottlenecks, and centralized audit records for […]</description>
    <pubDate>Tue, 08 Sep 2026 20:09:10 +0000</pubDate>
    <dc:creator>Oracle MySQL Group</dc:creator>
    <category>Monitoring</category>
    <category>MySQL</category>
    <category>MySQL Basics/How-To</category>
    <category>MySQL HeatWave</category>
    <category>mysql</category>
    <category>OCI Log Analytics</category>
  </item>

  <item>
    <title>Automating MySQL HeatWave Maintenance with the OCI Command Line Interface (CLI)</title>
    <guid isPermaLink="false">fd202ae4a50f8105059e16cb5dc5869b</guid>
    <link>https://blogs.oracle.com/mysql/automating-mysql-heatwave-maintenance-with-the-oci-cli</link>
    <description>Manage recurring maintenance windows, defer disruptive maintenance, and audit maintenance activity with a reusable Bash utility. MySQL HeatWave on Oracle Cloud Infrastructure gives administrators control over when scheduled maintenance can occur and, with maintenance-disabled windows, the ability to temporarily defer non-critical maintenance that would cause downtime. That flexibility is useful when a DB System supports […]</description>
    <pubDate>Tue, 08 Sep 2026 15:26:09 +0000</pubDate>
    <dc:creator>Oracle MySQL Group</dc:creator>
    <category>HeatWave AI</category>
    <category>MySQL</category>
    <category>MySQL Basics/How-To</category>
    <category>MySQL HeatWave</category>
  </item>

  <item>
    <title>Advanced Cryptography in MySQL with vsql-crypto</title>
    <guid isPermaLink="false">6a99edb652b52400017f4003</guid>
    <link>https://villagesql.com/blog/crypto/</link>
    <description>There are a few cryptography tasks that are common to many backends. For example, you should store passwords such that a database leak doesn't expose them, you should sign payloads so the receiving side can verify them, you should keep a column's data unreadable outside the database, and you should mint tokens an attacker cannot guess. MySQL's built-in functions cover part of that ground but fall short for many users. The SHA2() function will hash a password, but nothing salts it for you, so two people who pick the same password end up with the same stored value. MySQL does not have a built-in HMAC function. An HMAC is the keyed hash a receiver uses to tell a real message from a forged one.
When a database doesn't solve a problem natively, the work invariably moves up into application code, where every service reimplements it just a little bit differently. This adds ongoing maintenance complexity.
The vsql-crypto extension for VillageSQL Server adds a number of useful encryption and cryptographic capabilities to MySQL. It provides hashing, HMAC (Hash-based Message Authentication Code), salted password hashing, AES (Advanced Encryption Standard) encryption, and secure random generation, all backed by the OpenSSL cryptography library. For those moving from PostgreSQL or familiar with pgcrypto, it uses familiar function names: digest, hmac, crypt, gen_salt, encrypt, decrypt, gen_random_bytes, and gen_random_uuid.
VillageSQL is the innovation platform for MySQL that adds an extension framework (similar to PostgreSQL's extension framework) to enable permissionless innovation. Instead of waiting for a feature to be implemented in a few years in a future version of MySQL, new functionality can be dynamically added to a version of MySQL you run today. VillageSQL Server now supports MySQL 8.4, 9.7, and Percona Server 8.4. The vsql-crypto extension is an example of what we mean by permissionless innovation.
If you installed VillageSQL with the install script, the Docker image, or a release tarball, vsql-crypto comes bundled with the server. Installing the extension is then just one statement:
INSTALL EXTENSION vsql_crypto;

The rest of this post walks through four examples of how to implement common cryptography scenarios using vsql-crypto and VillageSQL: passwords that survive a database hack, signatures your webhooks can check, encryption that hides which rows match, and randomness worth trusting.



If you would rather stop reading here and have your AI agent demonstrate this for you, open this dropdown and copy the prompt into your preferred AI coding tool.

Set up a working demo of pgcrypto-style cryptography in MySQL on my machine,
using the vsql-crypto extension for VillageSQL. Work only against a local
throwaway server. If the only VillageSQL or MySQL server you find looks like
something I depend on, stop and ask me before touching it.

Do all of this yourself, and show me the real output of each step:

1. Find a VillageSQL server, or install one. The install script needs a codebase and a method:
   `curl -fsSL https://install.villagesql.com | VSQL_CODEBASE=mysql-8.4 INSTALL_METHOD=prebuilt bash`.
   Confirm with `SELECT VERSION()` before continuing. vsql-crypto needs no
   server flags, so do not turn on preview extensions for it.

2. Install the extension: `INSTALL EXTENSION vsql_crypto;`. It ships with the
   server, so there is nothing to download. Then read
   `INFORMATION_SCHEMA.EXTENSION_REGISTRATION` and tell me every function it
   registered, with argument and return types, and what `crypto_version()`
   reports.

3. Store a password. Hash one with `crypt` over a `gen_salt('pbkdf2-sha256', ...)`
   salt, show me the stored string, and read its parts back to me. Verify the
   right password and a wrong one in a single statement. Then hash the same
   password again at ten times the iteration count and show me that the older
   hash still verifies with no migration step. Time each verification over
   enough repetitions that the number means something, and tell me what the two
   timings cost an attacker.

4. Sign a payload. Compute an `hmac` over a JSON body with a shared secret, then
   show me what changes when you alter one byte of the body, and what changes
   when you alter the secret. Verify a signature in SQL the way a webhook
   receiver would, then tell me whether the comparison you wrote is
   constant-time and whether that matters here.

5. Encrypt a column. Create a table with a `VARBINARY` column, because
   ciphertext is binary, insert the same value twice through `encrypt`, and show
   me that the two stored ciphertexts differ. Do the same in a second table
   using the built-in `AES_ENCRYPT` and show me its answer, along with
   `@@block_encryption_mode`. Decrypt your own rows back. Then tell me which
   attack that difference stops. Also try `encrypt` in a generated column and
   show me what the server says.

6. Generate identifiers. Insert 1000 rows of `gen_random_uuid()` and 1000 rows
   of `HEX(gen_random_bytes(16))` into tables, then count the distinct values in
   each table. Tell me the largest `n` that `gen_random_bytes(n)` will accept
   and what it does past that.

7. Try to break it. One change at a time: decrypt with the wrong key, decrypt a
   ciphertext you flipped a byte in, encrypt with a key shorter than the cipher
   needs, name a cipher that does not exist, and ask `gen_salt` for pgcrypto's
   `bf` scheme. For each one, tell me whether it raised an error, returned NULL,
   or returned something that looks valid and is not, and quote exactly what
   came back.

Then give me a table of what you ran and what came back, tell me anything that
did not behave the way this asked, and drop every table and database you
created, leaving my server back where it started.





Scenario 1: Passwords that survive a database hack
A stolen user table that contains usernames and passwords can keep paying out to the attacker long after the breach. The attacker is off your network and working at their own pace, so the only thing standing between them and your users' passwords or other sensitive data is how those columns were written. Passwords should get hashed rather than encrypted, because anyone holding the key can undo encryption. You store a hash of each password instead. A hash is a one-way fingerprint: easy to compute from the password, but not reversible back into it. To check a login, you hash what the user typed and compare fingerprints.
That alone is not enough though, because the same password always produces the same fingerprint. An attacker who fingerprints a list of common passwords once can then match it against every account in every leak they have. A salt breaks that weakness. A salt is a random value generated per password, stored next to the hash, and mixed into the password before hashing. Identical passwords now produce different fingerprints, so each account has to be attacked on its own.
vsql-crypto hashes with PBKDF2 (Password-Based Key Derivation Function 2), which repeats a keyed hash of the password a number of times. The repetition is there to make the hashing slow on purpose, and what it spends is CPU time. At 100,000 repetitions one check takes roughly 7 milliseconds on a developer laptop, and at a million it takes about 70 milliseconds. Your user pays that once at login and never notices it. An attacker spends it on every password they try, and because each account carries its own salt, none of that work carries over to the next account.
Two functions do the work. gen_salt produces the random salt and records the iteration count with it, and crypt runs the password through PBKDF2. Verification recomputes the hash using the stored value as the salt, so the check is one comparison:
SET @hash = crypt('correct horse battery', gen_salt('pbkdf2-sha256', 100000));
SELECT crypt('correct horse battery', @hash) = @hash AS right_password,
       crypt('tr0ub4dor', @hash) = @hash AS wrong_password;
+----------------+----------------+
| right_password | wrong_password |
+----------------+----------------+
|              1 |              0 |
+----------------+----------------+

The stored string carries the algorithm and the iteration count in front of the salt and the hash:
SELECT crypt('correct horse battery', gen_salt('pbkdf2-sha256', 100000)) AS stored_hash;
+------------------------------------------------------------------------------------------+
| stored_hash                                                                              |
+------------------------------------------------------------------------------------------+
| $pbkdf2-sha256$100000$wxu24gc1Lg24l6WO0sQFYQ$j2DMnv+jgqZxTp+GMbhp+m/PeuO2T0iUNQKj9p99lzw |
+------------------------------------------------------------------------------------------+

Because every hash names its own cost, you can raise the iteration count for new passwords whenever hardware gets cheaper and leave the old rows alone. They keep verifying at the count they were written with, and each user moves up the next time they change their password.
Scenario 2: Signatures your webhooks can check
A webhook is a URL you publish so another system can call you when something happens, such as a payment clearing or a build finishing. It sits on the public internet, and anyone who learns the address can post to it too. So the receiver needs a way to separate a genuine callback from a forged one, and the sender needs a way to prove authorship without shipping a password on every request.
An HMAC does both. The sender hashes the payload together with a secret that only the two parties hold, and sends the resulting signature alongside the message. The receiver recomputes the signature from the payload it just got and its own copy of the secret. A match proves two things at once: the message came from someone holding the secret, and nobody altered it in transit. Change one byte of the payload and the signature no longer agrees.
With hmac in SQL, the database can sign an outgoing payload, or verify an incoming one, in the same statement that reads or writes it:
SELECT HEX(hmac('order-4711', 'shared-secret', 'sha256'));
+------------------------------------------------------------------+
| HEX(hmac('order-4711', 'shared-secret', 'sha256'))               |
+------------------------------------------------------------------+
| C5CAE86AA4B0CE3C183C346C5FED957FB3B2560136EA21CD1C5061A0D33A5AAF |
+------------------------------------------------------------------+

HEX() is there because an HMAC is raw bytes, and hex is how you put those in a header or compare them to what a sender sent. For plain hashing with no secret involved, digest accepts md5, sha1, sha224, sha256, sha384, and sha512.
Scenario 3: Encryption that hides which rows match
Some columns hold data you have to be able to read back: a card number, a bank account, a date of birth. A one-way fingerprint is no help there, so those columns get encrypted rather than hashed. encrypt turns the readable value, the plaintext, into scrambled bytes called the ciphertext, and the key turns it back.
Encryption happens one value at a time, so encrypting a column means each row's value is written as ciphertext and decrypted on the way out. That protects the data where your access controls end: in a backup file, on a disk that leaves the building, or in front of anyone who can read the data files but does not have your key.
The part that catches people is repetition. If the same plaintext always encrypts to the same ciphertext, then an attacker who never breaks the key still learns which rows hold the same value, and often the equality is the secret. For example, grouping an encrypted salary column tells you who is paid the same; grouping an encrypted diagnosis column tells you who shares a condition.
encrypt and decrypt run AES in CBC (Cipher Block Chaining) mode, which mixes each block of plaintext with the encrypted output of the block before it, and starts that chain from a random value called the initialization vector, or IV. encrypt draws a fresh IV on every single call, so in practice the same input never produces the same output twice. Ciphertext is binary, so the column that holds it is VARBINARY or BLOB:
CREATE TABLE secrets (c VARBINARY(255));
INSERT INTO secrets VALUES (encrypt('same message', 'my-secret-key-16', 'aes')),
                           (encrypt('same message', 'my-secret-key-16', 'aes'));
SELECT COUNT(*) AS rows_stored, COUNT(DISTINCT c) AS distinct_ciphertexts FROM secrets;
+-------------+----------------------+
| rows_stored | distinct_ciphertexts |
+-------------+----------------------+
|           2 |                    2 |
+-------------+----------------------+

The same value went in twice under the same key, and two different ciphertexts came out. Run those same two inserts through the built-in AES_ENCRYPT instead and the distinct count comes back as 1, because block_encryption_mode defaults to aes-128-ecb. ECB (Electronic Codebook) encrypts each block on its own with no IV, so identical input gives identical output every time. A GROUP BY on that column then sorts your users into buckets of equal values without anyone needing the key.
A random IV would normally be one more thing to store and hand back at decryption time. Here it is not: encrypt writes the IV into the front of the ciphertext and decrypt reads it from there, so decryption needs only the stored bytes, the key, and the cipher name:
SET @c = encrypt('Hello, World!', 'my-secret-key-16', 'aes');
SELECT CAST(decrypt(@c, 'my-secret-key-16', 'aes') AS CHAR) AS plaintext;
+---------------+
| plaintext     |
+---------------+
| Hello, World! |
+---------------+

Encryption hides the value, but it does not prove that nobody changed it. CBC carries no integrity check of its own, so if tampering is part of what you are defending against, store an hmac of the row alongside the ciphertext and check it before you trust what comes back.
aes and aes-128 name the same cipher, and aes-192 and aes-256 take the longer keys. An unrecognized cipher name returns NULL, while a key shorter than the cipher requires stops the statement, because silently padding a weak key would be worse than failing:
SELECT encrypt('data', 'short', 'aes-256');
ERROR 3200 (HY000): VDF error in function 'encrypt': key too short for aes-256: need 32 bytes, got 5

A fresh IV per call is also why the server won't let encrypt compute a generated column, a column whose value MySQL derives from other columns. A generated column has to give the same answer every time it is recomputed, encrypt deliberately does not, and ERROR 3763 says so. Encrypt the value in the INSERT or UPDATE that writes the row instead.
Scenario 4: Randomness worth trusting
A session token, a password reset link, and an API key are all secrets that hold only as long as nobody can guess them. That is a higher bar than being unique, and it is where MySQL's own UUID() falls down. A UUID (Universally Unique Identifier) comes in several versions, and UUID() returns a version 1, which is built from the current time and the server's network address rather than from bits an attacker cannot predict. Ask for three in a row and you can see it incrementing:
SELECT UUID() FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3) t;
+--------------------------------------+
| UUID()                               |
+--------------------------------------+
| b95ff010-a7d2-11f1-8e99-2a389a5e0709 |
| b95ff011-a7d2-11f1-8e99-2a389a5e0709 |
| b95ff012-a7d2-11f1-8e99-2a389a5e0709 |
+--------------------------------------+

Everything after the first field is shared, and the first field counts up. Hand one of those to a user as a reset link and you have handed them their neighbors' links as well.
gen_random_uuid() returns a version 4 UUID, which is random bits apart from the six that mark the version and the variant, and gen_random_bytes(n) returns up to 1024 raw random bytes for cases where you want your own format. Both draw from OpenSSL's cryptographically secure generator, which is built to be unpredictable even to someone who has already seen its earlier output:
SELECT gen_random_uuid();
+--------------------------------------+
| gen_random_uuid()                    |
+--------------------------------------+
| 758e918c-4096-4b55-b192-150e3fa9d8ea |
+--------------------------------------+

The 4 opening the third group marks the version, and the first digit of the fourth group is a variant marker that is always 8, 9, a, or b. Every other digit is drawn fresh, with no clock and no network address for an attacker to work forward from.
gen_random_uuid() covers what this section is about: one value nobody can guess, generated on the spot. If UUIDs are a column you store, index, and read back, reach for the vsql-uuid extension, which is bundled with the server as well. It adds a real uuid type that holds 16 bytes where a VARCHAR(36) column needs 36 characters, and its UUID_V7() is the generator worth knowing about. A version 7 value opens with a millisecond timestamp, so values written a millisecond or more apart sort in that same order, while version 4 values sort at random. The tail stays random, which is what makes version 7 the usual choice for a primary key you also want to be unguessable.
Try it out
Please try vsql-crypto on your own data, and tell us how we can improve it. The README is the full reference for every function, argument, and error case. Find us on Discord or leave an issue on vsql-crypto.
To get started with VillageSQL Server, go to villagesql.com.</description>
    <content:encoded><![CDATA[<img src="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/images/2026/09/bridge-in-fog.jpg" alt="Advanced Cryptography in MySQL with vsql-crypto"><p>There are a few cryptography tasks that are common to many backends. For example, you should store passwords such that a database leak doesn't expose them, you should sign payloads so the receiving side can verify them, you should keep a column's data unreadable outside the database, and you should mint tokens an attacker cannot guess. MySQL's built-in functions cover part of that ground but fall short for many users. The <code>SHA2()</code> function will hash a password, but nothing salts it for you, so two people who pick the same password end up with the same stored value. MySQL does not have a built-in HMAC function. An HMAC is the keyed hash a receiver uses to tell a real message from a forged one.</p>
<p>When a database doesn't solve a problem natively, the work invariably moves up into application code, where every service reimplements it just a little bit differently. This adds ongoing maintenance complexity.</p>
<p>The <a href="https://github.com/villagesql/vsql-crypto">vsql-crypto</a> extension for VillageSQL Server adds a number of useful encryption and cryptographic capabilities to MySQL. It provides hashing, HMAC (Hash-based Message Authentication Code), salted password hashing, AES (Advanced Encryption Standard) encryption, and secure random generation, all backed by the OpenSSL cryptography library. For those moving from PostgreSQL or familiar with pgcrypto, it uses familiar function names: <code>digest</code>, <code>hmac</code>, <code>crypt</code>, <code>gen_salt</code>, <code>encrypt</code>, <code>decrypt</code>, <code>gen_random_bytes</code>, and <code>gen_random_uuid</code>.</p>
<p>VillageSQL is the innovation platform for MySQL that adds an extension framework (similar to PostgreSQL's extension framework) to enable permissionless innovation. Instead of waiting for a feature to be implemented in a few years in a future version of MySQL, new functionality can be dynamically added to a version of MySQL you run today. VillageSQL Server now supports MySQL 8.4, 9.7, and Percona Server 8.4. The vsql-crypto extension is an example of what we mean by permissionless innovation.</p>
<p>If you installed VillageSQL with the <a href="https://villagesql.com/install">install script</a>, the Docker image, or a release tarball, vsql-crypto comes bundled with the server. Installing the extension is then just one statement:</p>
<pre><code class="language-sql">INSTALL EXTENSION vsql_crypto;
</code></pre>
<p>The rest of this post walks through four examples of how to implement common cryptography scenarios using vsql-crypto and VillageSQL: passwords that survive a database hack, signatures your webhooks can check, encryption that hides which rows match, and randomness worth trusting.</p>
<hr>
<!--kg-card-begin: html-->
<details>
<summary><strong>If you would rather stop reading here and have your AI agent demonstrate this for you, open this dropdown and copy the prompt into your preferred AI coding tool.</strong></summary>
<pre>
Set up a working demo of pgcrypto-style cryptography in MySQL on my machine,
using the vsql-crypto extension for VillageSQL. Work only against a local
throwaway server. If the only VillageSQL or MySQL server you find looks like
something I depend on, stop and ask me before touching it.

Do all of this yourself, and show me the real output of each step:

1. Find a VillageSQL server, or install one. The install script needs a codebase and a method:
   `curl -fsSL https://install.villagesql.com | VSQL_CODEBASE=mysql-8.4 INSTALL_METHOD=prebuilt bash`.
   Confirm with `SELECT VERSION()` before continuing. vsql-crypto needs no
   server flags, so do not turn on preview extensions for it.

2. Install the extension: `INSTALL EXTENSION vsql_crypto;`. It ships with the
   server, so there is nothing to download. Then read
   `INFORMATION_SCHEMA.EXTENSION_REGISTRATION` and tell me every function it
   registered, with argument and return types, and what `crypto_version()`
   reports.

3. Store a password. Hash one with `crypt` over a `gen_salt('pbkdf2-sha256', ...)`
   salt, show me the stored string, and read its parts back to me. Verify the
   right password and a wrong one in a single statement. Then hash the same
   password again at ten times the iteration count and show me that the older
   hash still verifies with no migration step. Time each verification over
   enough repetitions that the number means something, and tell me what the two
   timings cost an attacker.

4. Sign a payload. Compute an `hmac` over a JSON body with a shared secret, then
   show me what changes when you alter one byte of the body, and what changes
   when you alter the secret. Verify a signature in SQL the way a webhook
   receiver would, then tell me whether the comparison you wrote is
   constant-time and whether that matters here.

5. Encrypt a column. Create a table with a `VARBINARY` column, because
   ciphertext is binary, insert the same value twice through `encrypt`, and show
   me that the two stored ciphertexts differ. Do the same in a second table
   using the built-in `AES_ENCRYPT` and show me its answer, along with
   `@@block_encryption_mode`. Decrypt your own rows back. Then tell me which
   attack that difference stops. Also try `encrypt` in a generated column and
   show me what the server says.

6. Generate identifiers. Insert 1000 rows of `gen_random_uuid()` and 1000 rows
   of `HEX(gen_random_bytes(16))` into tables, then count the distinct values in
   each table. Tell me the largest `n` that `gen_random_bytes(n)` will accept
   and what it does past that.

7. Try to break it. One change at a time: decrypt with the wrong key, decrypt a
   ciphertext you flipped a byte in, encrypt with a key shorter than the cipher
   needs, name a cipher that does not exist, and ask `gen_salt` for pgcrypto's
   `bf` scheme. For each one, tell me whether it raised an error, returned NULL,
   or returned something that looks valid and is not, and quote exactly what
   came back.

Then give me a table of what you ran and what came back, tell me anything that
did not behave the way this asked, and drop every table and database you
created, leaving my server back where it started.
</pre>
</details>

<hr>
<!--kg-card-end: html-->
<h2>Scenario 1: Passwords that survive a database hack</h2>
<p>A stolen user table that contains usernames and passwords can keep paying out to the attacker long after the breach. The attacker is off your network and working at their own pace, so the only thing standing between them and your users' passwords or other sensitive data is how those columns were written. Passwords should get hashed rather than encrypted, because anyone holding the key can undo encryption. You store a hash of each password instead. A hash is a one-way fingerprint: easy to compute from the password, but not reversible back into it. To check a login, you hash what the user typed and compare fingerprints.</p>
<p>That alone is not enough though, because the same password always produces the same fingerprint. An attacker who fingerprints a list of common passwords once can then match it against every account in every leak they have. A salt breaks that weakness. A salt is a random value generated per password, stored next to the hash, and mixed into the password before hashing. Identical passwords now produce different fingerprints, so each account has to be attacked on its own.</p>
<p>vsql-crypto hashes with PBKDF2 (Password-Based Key Derivation Function 2), which repeats a keyed hash of the password a number of times. The repetition is there to make the hashing slow on purpose, and what it spends is CPU time. At 100,000 repetitions one check takes roughly 7 milliseconds on a developer laptop, and at a million it takes about 70 milliseconds. Your user pays that once at login and never notices it. An attacker spends it on every password they try, and because each account carries its own salt, none of that work carries over to the next account.</p>
<p>Two functions do the work. <code>gen_salt</code> produces the random salt and records the iteration count with it, and <code>crypt</code> runs the password through PBKDF2. Verification recomputes the hash using the stored value as the salt, so the check is one comparison:</p>
<pre><code class="language-sql">SET @hash = crypt('correct horse battery', gen_salt('pbkdf2-sha256', 100000));
SELECT crypt('correct horse battery', @hash) = @hash AS right_password,
       crypt('tr0ub4dor', @hash) = @hash AS wrong_password;
+----------------+----------------+
| right_password | wrong_password |
+----------------+----------------+
|              1 |              0 |
+----------------+----------------+
</code></pre>
<p>The stored string carries the algorithm and the iteration count in front of the salt and the hash:</p>
<pre><code class="language-sql">SELECT crypt('correct horse battery', gen_salt('pbkdf2-sha256', 100000)) AS stored_hash;
+------------------------------------------------------------------------------------------+
| stored_hash                                                                              |
+------------------------------------------------------------------------------------------+
| $pbkdf2-sha256$100000$wxu24gc1Lg24l6WO0sQFYQ$j2DMnv+jgqZxTp+GMbhp+m/PeuO2T0iUNQKj9p99lzw |
+------------------------------------------------------------------------------------------+
</code></pre>
<p>Because every hash names its own cost, you can raise the iteration count for new passwords whenever hardware gets cheaper and leave the old rows alone. They keep verifying at the count they were written with, and each user moves up the next time they change their password.</p>
<h2>Scenario 2: Signatures your webhooks can check</h2>
<p>A webhook is a URL you publish so another system can call you when something happens, such as a payment clearing or a build finishing. It sits on the public internet, and anyone who learns the address can post to it too. So the receiver needs a way to separate a genuine callback from a forged one, and the sender needs a way to prove authorship without shipping a password on every request.</p>
<p>An HMAC does both. The sender hashes the payload together with a secret that only the two parties hold, and sends the resulting signature alongside the message. The receiver recomputes the signature from the payload it just got and its own copy of the secret. A match proves two things at once: the message came from someone holding the secret, and nobody altered it in transit. Change one byte of the payload and the signature no longer agrees.</p>
<p>With <code>hmac</code> in SQL, the database can sign an outgoing payload, or verify an incoming one, in the same statement that reads or writes it:</p>
<pre><code class="language-sql">SELECT HEX(hmac('order-4711', 'shared-secret', 'sha256'));
+------------------------------------------------------------------+
| HEX(hmac('order-4711', 'shared-secret', 'sha256'))               |
+------------------------------------------------------------------+
| C5CAE86AA4B0CE3C183C346C5FED957FB3B2560136EA21CD1C5061A0D33A5AAF |
+------------------------------------------------------------------+
</code></pre>
<p><code>HEX()</code> is there because an HMAC is raw bytes, and hex is how you put those in a header or compare them to what a sender sent. For plain hashing with no secret involved, <code>digest</code> accepts <code>md5</code>, <code>sha1</code>, <code>sha224</code>, <code>sha256</code>, <code>sha384</code>, and <code>sha512</code>.</p>
<h2>Scenario 3: Encryption that hides which rows match</h2>
<p>Some columns hold data you have to be able to read back: a card number, a bank account, a date of birth. A one-way fingerprint is no help there, so those columns get encrypted rather than hashed. <code>encrypt</code> turns the readable value, the plaintext, into scrambled bytes called the ciphertext, and the key turns it back.</p>
<p>Encryption happens one value at a time, so encrypting a column means each row's value is written as ciphertext and decrypted on the way out. That protects the data where your access controls end: in a backup file, on a disk that leaves the building, or in front of anyone who can read the data files but does not have your key.</p>
<p>The part that catches people is repetition. If the same plaintext always encrypts to the same ciphertext, then an attacker who never breaks the key still learns which rows hold the same value, and often the equality is the secret. For example, grouping an encrypted salary column tells you who is paid the same; grouping an encrypted diagnosis column tells you who shares a condition.</p>
<p><code>encrypt</code> and <code>decrypt</code> run AES in CBC (Cipher Block Chaining) mode, which mixes each block of plaintext with the encrypted output of the block before it, and starts that chain from a random value called the initialization vector, or IV. <code>encrypt</code> draws a fresh IV on every single call, so in practice the same input never produces the same output twice. Ciphertext is binary, so the column that holds it is <code>VARBINARY</code> or <code>BLOB</code>:</p>
<pre><code class="language-sql">CREATE TABLE secrets (c VARBINARY(255));
INSERT INTO secrets VALUES (encrypt('same message', 'my-secret-key-16', 'aes')),
                           (encrypt('same message', 'my-secret-key-16', 'aes'));
SELECT COUNT(*) AS rows_stored, COUNT(DISTINCT c) AS distinct_ciphertexts FROM secrets;
+-------------+----------------------+
| rows_stored | distinct_ciphertexts |
+-------------+----------------------+
|           2 |                    2 |
+-------------+----------------------+
</code></pre>
<p>The same value went in twice under the same key, and two different ciphertexts came out. Run those same two inserts through the built-in <code>AES_ENCRYPT</code> instead and the distinct count comes back as 1, because <code>block_encryption_mode</code> defaults to <code>aes-128-ecb</code>. ECB (Electronic Codebook) encrypts each block on its own with no IV, so identical input gives identical output every time. A <code>GROUP BY</code> on that column then sorts your users into buckets of equal values without anyone needing the key.</p>
<p>A random IV would normally be one more thing to store and hand back at decryption time. Here it is not: <code>encrypt</code> writes the IV into the front of the ciphertext and <code>decrypt</code> reads it from there, so decryption needs only the stored bytes, the key, and the cipher name:</p>
<pre><code class="language-sql">SET @c = encrypt('Hello, World!', 'my-secret-key-16', 'aes');
SELECT CAST(decrypt(@c, 'my-secret-key-16', 'aes') AS CHAR) AS plaintext;
+---------------+
| plaintext     |
+---------------+
| Hello, World! |
+---------------+
</code></pre>
<p>Encryption hides the value, but it does not prove that nobody changed it. CBC carries no integrity check of its own, so if tampering is part of what you are defending against, store an <code>hmac</code> of the row alongside the ciphertext and check it before you trust what comes back.</p>
<p><code>aes</code> and <code>aes-128</code> name the same cipher, and <code>aes-192</code> and <code>aes-256</code> take the longer keys. An unrecognized cipher name returns NULL, while a key shorter than the cipher requires stops the statement, because silently padding a weak key would be worse than failing:</p>
<pre><code class="language-sql">SELECT encrypt('data', 'short', 'aes-256');
ERROR 3200 (HY000): VDF error in function 'encrypt': key too short for aes-256: need 32 bytes, got 5
</code></pre>
<p>A fresh IV per call is also why the server won't let <code>encrypt</code> compute a generated column, a column whose value MySQL derives from other columns. A generated column has to give the same answer every time it is recomputed, <code>encrypt</code> deliberately does not, and <code>ERROR 3763</code> says so. Encrypt the value in the <code>INSERT</code> or <code>UPDATE</code> that writes the row instead.</p>
<h2>Scenario 4: Randomness worth trusting</h2>
<p>A session token, a password reset link, and an API key are all secrets that hold only as long as nobody can guess them. That is a higher bar than being unique, and it is where MySQL's own <code>UUID()</code> falls down. A UUID (Universally Unique Identifier) comes in several versions, and <code>UUID()</code> returns a version 1, which is built from the current time and the server's network address rather than from bits an attacker cannot predict. Ask for three in a row and you can see it incrementing:</p>
<pre><code class="language-sql">SELECT UUID() FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3) t;
+--------------------------------------+
| UUID()                               |
+--------------------------------------+
| b95ff010-a7d2-11f1-8e99-2a389a5e0709 |
| b95ff011-a7d2-11f1-8e99-2a389a5e0709 |
| b95ff012-a7d2-11f1-8e99-2a389a5e0709 |
+--------------------------------------+
</code></pre>
<p>Everything after the first field is shared, and the first field counts up. Hand one of those to a user as a reset link and you have handed them their neighbors' links as well.</p>
<p><code>gen_random_uuid()</code> returns a version 4 UUID, which is random bits apart from the six that mark the version and the variant, and <code>gen_random_bytes(n)</code> returns up to 1024 raw random bytes for cases where you want your own format. Both draw from OpenSSL's cryptographically secure generator, which is built to be unpredictable even to someone who has already seen its earlier output:</p>
<pre><code class="language-sql">SELECT gen_random_uuid();
+--------------------------------------+
| gen_random_uuid()                    |
+--------------------------------------+
| 758e918c-4096-4b55-b192-150e3fa9d8ea |
+--------------------------------------+
</code></pre>
<p>The <code>4</code> opening the third group marks the version, and the first digit of the fourth group is a variant marker that is always 8, 9, <code>a</code>, or <code>b</code>. Every other digit is drawn fresh, with no clock and no network address for an attacker to work forward from.</p>
<p><code>gen_random_uuid()</code> covers what this section is about: one value nobody can guess, generated on the spot. If UUIDs are a column you store, index, and read back, reach for the <a href="https://github.com/villagesql/vsql-uuid">vsql-uuid</a> extension, which is bundled with the server as well. It adds a real <code>uuid</code> type that holds 16 bytes where a <code>VARCHAR(36)</code> column needs 36 characters, and its <code>UUID_V7()</code> is the generator worth knowing about. A version 7 value opens with a millisecond timestamp, so values written a millisecond or more apart sort in that same order, while version 4 values sort at random. The tail stays random, which is what makes version 7 the usual choice for a primary key you also want to be unguessable.</p>
<h2>Try it out</h2>
<p>Please try vsql-crypto on your own data, and tell us how we can improve it. The <a href="https://github.com/villagesql/vsql-crypto">README</a> is the full reference for every function, argument, and error case. Find us on <a href="https://discord.gg/KSr6whd3Fr">Discord</a> or leave an issue on <a href="https://github.com/villagesql/vsql-crypto/issues">vsql-crypto</a>.</p>
<p>To get started with VillageSQL Server, go to <a href="https://villagesql.com/">villagesql.com</a>.</p>]]></content:encoded>
    <pubDate>Tue, 08 Sep 2026 11:48:22 +0000</pubDate>
    <dc:creator>VillageSQL</dc:creator>
    <category>Extension</category>
    <category>MySQL</category>
    <category>crypto</category>
    <category>Open Source</category>
    <category>VillageSQL</category>
    <category>AI</category>
  </item>

  <item>
    <title>ProxySQL HA with BGP ECMP Anycast</title>
    <guid isPermaLink="false">https://percona.community/blog/2026/09/07/proxysql-ha-with-bgp-ecmp-anycast/</guid>
    <link>https://percona.community/blog/2026/09/07/proxysql-ha-with-bgp-ecmp-anycast/</link>
    <description>When setting up a new database for an application, high availability (HA) is one of the main priorities. Let’s assume for this example that you chose to use a Percona XtraDB (PXC) cluster to host your database.
But how does the application know which PXC node is healthy and can receive application traffic? Introducing a cluster of ProxySQLs can solve this problem, as ProxySQL will healthcheck the database nodes and route the application traffic to the healthy nodes.
However, now the HA problem comes up again: how does the application know which ProxySQL host is healthy?
Putting “one more component” in front of your servers to make them highly available just shifts the problem up by one layer.
From making the database HA, to making ProxySQL HA, to needing to make HAProxy HA and so on…
At some point, you may land on the common HA strategy, keepalived, but BGP ECMP is a powerful alternative worth considering.
What is BGP, what is ECMP?
As a brief summary, Border Gateway Protocol (BGP) is a routing protocol which operates over TCP. Equal-Cost Multi-Path (ECMP) defines that we want the traffic to be loadbalanced equally across the given routes.
Routers acting as BGP speakers are configured to accept routes for defined IP ranges and Autonomous System (AS) numbers, storing information about the networks that the router can reach in a Routing Information Base (RIB) table.
To benefit from Anycast, we will assign both/all ProxySQL nodes the same virtual IP address. BGP is then used to let the router know multiple routes to reach that IP. Configuring ECMP will cause the router to balance the traffic over these routes.
User defined health checks are executed by a BGP-speaking daemon, such as ExaBGP, running on the host to ensure that the router has the correct information about the status of a route.
As soon as a health check fails, ExaBGP will trigger a BGP update to withdraw the route. The router will remove the entry from its RIB table and stop forwarding packets to that host.
The traffic will be redistributed to the remaining healthy ProxySQL nodes.
For a more in depth guide about BGP please refer to Cisco press.
How ECMP based on BGP works
For our setup we can’t have the applications connect to the normal IPs of the ProxySQL nodes, as these are assigned to one node and fixed.
Instead we need a single separate anycast IP (let’s take 10.5.200.1/32) that we can assign to both ProxySQL nodes. This IP will be assigned to the loopback interface and has to be of a separate network, not overlapping with the IP-Range the normal ProxySQL node IPs are from.
When a packet with the destination set to the ProxySQL anycast IP (10.5.200.1) arrives at the router, the router checks its internal routing table to determine the next hop for the packet.
The router will see the two ProxySQL nodes in the cluster as potential next hop (as they both have the same anycast IP) and will pick one of the ProxySQL nodes, based on ECMP, to forward the packet to.
In ECMP the next hop is dynamically decided based on a 5-tuple hash from the packet header fields:
{ source IP address | destination IP address | protocol | source port | destination port }
Because the IP address and ports are in the hash, this ensures that the packets belonging to the same TCP stream are kept on the same path, to prevent packets of the same TCP connection ending up on multiple ProxySQL nodes.
You can visualise the setup like this:


By doing so, we have achieved high availability by leveraging BGP ECMP to loadbalance traffic in an active/active configuration across the ProxySQL nodes.
Additionally, the application config can be simplified, as only the single anycast IP (or DNS record for that IP) needs to be used for the ProxySQL cluster, and the logic of routing will be handled by the router.
Failures of a ProxySQL host are automatically handled by ExaBGP; a BGP update is sent to the router, and the route to the failed ProxySQL is withdrawn. The router redirects traffic to the remaining healthy ProxySQL, with no manual interventions required.
Extra infrastructure components (such as internal loadbalancers) can be avoided, eliminating additional network hops and improving network latency.
Alternative strategies for using BGP with databases
Of course, a ProxySQL cluster is not the only way to leverage BGP ECMP in order to achieve high availability for your databases. Some other strategies could be to use it for read-replica routing, local traffic routing, or for routing towards loadbalancers (e.g. haproxy).
Routing towards the database loadbalancer
Using BGP ECMP does not mean that you have to forego a database loadbalancer. You can configure your database servers to sit behind a database loadbalancer,
such as HAproxy or ProxySQL, and implement BGP ECMP in order to route traffic towards the loadbalancers and operate them highly available as true active/active pair.
If one of the loadbalancer instances dies, then BGP automatically takes care of routing the traffic to the remaining healthy peers for you.
Read replica routing
You can use BGP ECMP to distribute MySQL-Connections over multiple read replicas without using any Loadbalancer / ProxySQL at all. This saves you the additional latency and network hops of using a loadbalancer/ProxySQL.
If you need to ensure that you do not read from a replica which is lagging behind or has stopped replicating, you can implement this logic in the BGP health checks.
BGP Local Preference
If you have a multi-datacenter setup, you can choose to use local preference to keep traffic localised within the same datacenter. For example if you have an application server and a proxysql host in one datacenter (datacenter A), and an application server and proxysql host in a second datacenter (datacenter B),
you can tell the router to send traffic from the application to the proxysql within the same datacenter. The advantage of this is that it keeps network latency low, and avoids cross-site transit. Configuring this in BGP means that the application does not need to be aware of which datacenter it is running in. The BGP router handles localised routing for you.
If the local route would disappear, then the BGP router would automatically divert traffic from the application in datacenter A to the proxysql in the datacenter B.


Advantages of BGP

One advantage of BGP over keepalived is that your anycast-nodes don’t need to be in the same subnet (especially useful for multi-datacenter setups). Keepalived instead requires the nodes to be part of the same Layer 2 Network.
BGP ECMP supports active/active, unlike keepalived which only supports active/passive architectures.
In BGP, the router has the overview of which node is healthy or unhealthy. In keepalived this knowledge resides in the keepalived process running on the node itself.
As long as the nodes running keepalived can see each other, keepalived thinks everything is fine, but the nodes might have lost connection to the router.
Whereas with BGP, health checks ensure that BGP is aware of the state of the route. In case there would be a network problem that would make the node unreachable, the route would disappear from the router.
You can horizontally scale the nodes with BGP.
BGP Local preference allows you to automatically route traffic within a datacenter, without the application needing to configure logic like “use ProxySQL-A when running in datacenter-A, otherwise ProxySQL-B.”
You can set BGP to eliminate additional hops of infrastructure components, for example connecting to a pool of read replicas, without needing to connect over a loadbalancer.

Caveats with BGP
BGP ECMP is not connection-state aware, so if instances disappear/die or new instances join and the RIB table is rebuilt, the hashing algorithm will most likely forward packets for existing connections to a different instance than before. As that instance will not be aware of this TCP-Connection, it will send an RST-packet and the application will have to re-open its database connection.
In the next post, we will explain the technical details of setting up BGP ECMP for our ProxySQL cluster using OPNsense as Router.
This post is part of the Percona Community Writers Program.</description>
    <content:encoded><![CDATA[<p>When setting up a new database for an application, high availability (HA) is one of the main priorities. Let’s assume for this example that you chose to use a Percona XtraDB (PXC) cluster to host your database.
But how does the application know which PXC node is healthy and can receive application traffic? Introducing a cluster of ProxySQLs can solve this problem, as ProxySQL will healthcheck the database nodes and route the application traffic to the healthy nodes.
However, now the HA problem comes up again: how does the application know which ProxySQL host is healthy?</p>
<p>Putting “one more component” in front of your servers to make them highly available just shifts the problem up by one layer.
From making the database HA, to making ProxySQL HA, to needing to make HAProxy HA and so on…</p>
<p>At some point, you may land on the common HA strategy, keepalived, but BGP ECMP is a powerful alternative worth considering.</p>
<h2>What is BGP, what is ECMP?</h2>
<p>As a brief summary, Border Gateway Protocol (BGP) is a routing protocol which operates over TCP. Equal-Cost Multi-Path (ECMP) defines that we want the traffic to be loadbalanced equally across the given routes.
Routers acting as BGP speakers are configured to accept routes for defined IP ranges and Autonomous System (AS) numbers, storing information about the networks that the router can reach in a Routing Information Base (RIB) table.
To benefit from Anycast, we will assign both/all ProxySQL nodes the same virtual IP address. BGP is then used to let the router know multiple routes to reach that IP. Configuring ECMP will cause the router to balance the traffic over these routes.</p>
<p>User defined health checks are executed by a BGP-speaking daemon, such as ExaBGP, running on the host to ensure that the router has the correct information about the status of a route.
As soon as a health check fails, ExaBGP will trigger a BGP update to withdraw the route. The router will remove the entry from its RIB table and stop forwarding packets to that host.
The traffic will be redistributed to the remaining healthy ProxySQL nodes.</p>
<p>For a more in depth guide about BGP please refer to <a href="https://www.ciscopress.com/articles/article.asp?p=2738462&amp;seqNum=2" target="_blank" rel="noopener noreferrer">Cisco press</a>.</p>
<h2>How ECMP based on BGP works</h2>
<p>For our setup we can’t have the applications connect to the normal IPs of the ProxySQL nodes, as these are assigned to one node and fixed.
Instead we need a single separate anycast IP (let’s take 10.5.200.1/32) that we can assign to both ProxySQL nodes. This IP will be assigned to the loopback interface and has to be of a separate network, not overlapping with the IP-Range the normal ProxySQL node IPs are from.</p>
<p>When a packet with the destination set to the ProxySQL anycast IP (10.5.200.1) arrives at the router, the router checks its internal routing table to determine the next hop for the packet.
The router will see the two ProxySQL nodes in the cluster as potential next hop (as they both have the same anycast IP) and will pick one of the ProxySQL nodes, based on ECMP, to forward the packet to.
In ECMP the next hop is dynamically decided based on a 5-tuple hash from the packet header fields:</p>
<p><code>{ source IP address | destination IP address | protocol | source port | destination port }</code></p>
<p>Because the IP address and ports are in the hash, this ensures that the packets belonging to the same TCP stream are kept on the same path, to prevent packets of the same TCP connection ending up on multiple ProxySQL nodes.</p>
<p>You can visualise the setup like this:</p>
<p>
<figure><img width="1383" height="1024" sizes="(max-width: 1383px) 100vw, 1383px" srcset="https://percona.community/blog/2026/09/proxysql-ha-with-bgp-ecmp-anycast-diagram_hu_e3abd1775fb0f508.webp 768w, https://percona.community/blog/2026/09/proxysql-ha-with-bgp-ecmp-anycast-diagram_hu_b243bca257bc4498.webp 480w, https://percona.community/blog/2026/09/proxysql-ha-with-bgp-ecmp-anycast-diagram_hu_fb1e65d520c6a31d.webp 1383w" src="https://percona.community/blog/2026/09/proxysql-ha-with-bgp-ecmp-anycast-diagram_hu_fb1e65d520c6a31d.webp" alt="Diagram" loading="lazy"></figure></p>
<p>By doing so, we have achieved high availability by leveraging BGP ECMP to loadbalance traffic in an active/active configuration across the ProxySQL nodes.
Additionally, the application config can be simplified, as only the single anycast IP (or DNS record for that IP) needs to be used for the ProxySQL cluster, and the logic of routing will be handled by the router.
Failures of a ProxySQL host are automatically handled by ExaBGP; a BGP update is sent to the router, and the route to the failed ProxySQL is withdrawn. The router redirects traffic to the remaining healthy ProxySQL, with no manual interventions required.
Extra infrastructure components (such as internal loadbalancers) can be avoided, eliminating additional network hops and improving network latency.</p>
<h2>Alternative strategies for using BGP with databases</h2>
<p>Of course, a ProxySQL cluster is not the only way to leverage BGP ECMP in order to achieve high availability for your databases. Some other strategies could be to use it for read-replica routing, local traffic routing, or for routing towards loadbalancers (e.g. haproxy).</p>
<h3>Routing towards the database loadbalancer</h3>
<p>Using BGP ECMP does not mean that you have to forego a database loadbalancer. You can configure your database servers to sit behind a database loadbalancer,
such as HAproxy or ProxySQL, and implement BGP ECMP in order to route traffic towards the loadbalancers and operate them highly available as true active/active pair.
If one of the loadbalancer instances dies, then BGP automatically takes care of routing the traffic to the remaining healthy peers for you.</p>
<h3>Read replica routing</h3>
<p>You can use BGP ECMP to distribute MySQL-Connections over multiple read replicas without using any Loadbalancer / ProxySQL at all. This saves you the additional latency and network hops of using a loadbalancer/ProxySQL.
If you need to ensure that you do not read from a replica which is lagging behind or has stopped replicating, you can implement this logic in the BGP health checks.</p>
<h3>BGP Local Preference</h3>
<p>If you have a multi-datacenter setup, you can choose to use local preference to keep traffic localised within the same datacenter. For example if you have an application server and a proxysql host in one datacenter (datacenter A), and an application server and proxysql host in a second datacenter (datacenter B),
you can tell the router to send traffic from the application to the proxysql within the same datacenter. The advantage of this is that it keeps network latency low, and avoids cross-site transit. Configuring this in BGP means that the application does not need to be aware of which datacenter it is running in. The BGP router handles localised routing for you.
If the local route would disappear, then the BGP router would automatically divert traffic from the application in datacenter A to the proxysql in the datacenter B.</p>
<p>
<figure><img width="1619" height="971" sizes="(max-width: 1400px) 100vw, 1400px" srcset="https://percona.community/blog/2026/09/proxysql-ha-with-bgp-ecmp-anycast-lpref_hu_d72465f4c8faa894.webp 768w, https://percona.community/blog/2026/09/proxysql-ha-with-bgp-ecmp-anycast-lpref_hu_7cbcd1618bda23e3.webp 480w, https://percona.community/blog/2026/09/proxysql-ha-with-bgp-ecmp-anycast-lpref_hu_6bf20dfe86744131.webp 1400w" src="https://percona.community/blog/2026/09/proxysql-ha-with-bgp-ecmp-anycast-lpref_hu_6bf20dfe86744131.webp" alt="Diagram BGP local preference" loading="lazy"></figure></p>
<h2>Advantages of BGP</h2>
<ul>
<li>One advantage of BGP over keepalived is that your anycast-nodes don’t need to be in the same subnet (especially useful for multi-datacenter setups). Keepalived instead requires the nodes to be part of the same Layer 2 Network.</li>
<li>BGP ECMP supports active/active, unlike keepalived which only supports active/passive architectures.</li>
<li>In BGP, the router has the overview of which node is healthy or unhealthy. In keepalived this knowledge resides in the keepalived process running on the node itself.
As long as the nodes running keepalived can see each other, keepalived thinks everything is fine, but the nodes might have lost connection to the router.
Whereas with BGP, health checks ensure that BGP is aware of the state of the route. In case there would be a network problem that would make the node unreachable, the route would disappear from the router.</li>
<li>You can horizontally scale the nodes with BGP.</li>
<li>BGP Local preference allows you to automatically route traffic within a datacenter, without the application needing to configure logic like “use ProxySQL-A when running in datacenter-A, otherwise ProxySQL-B.”</li>
<li>You can set BGP to eliminate additional hops of infrastructure components, for example connecting to a pool of read replicas, without needing to connect over a loadbalancer.</li>
</ul>
<h2>Caveats with BGP</h2>
<p>BGP ECMP is not connection-state aware, so if instances disappear/die or new instances join and the RIB table is rebuilt, the hashing algorithm will most likely forward packets for existing connections to a different instance than before. As that instance will not be aware of this TCP-Connection, it will send an RST-packet and the application will have to re-open its database connection.</p>
<p>In the next post, we will explain the technical details of setting up BGP ECMP for our ProxySQL cluster using OPNsense as Router.</p>
<p><em>This post is part of the <a href="https://percona.community/blog/write-for-percona-community/">Percona Community Writers Program</a>.</em></p>]]></content:encoded>
    <pubDate>Mon, 07 Sep 2026 11:00:00 +0000</pubDate>
    <dc:creator>Percona Community</dc:creator>
    <category>MySQL</category>
    <category>ProxySQL</category>
    <category>Opensource</category>
    <category>BGP</category>
    <category>Percona Server</category>
    <category>DevOps</category>
  </item>

  <item>
    <title>Something restarts my MySQL every morning. It isn’t MySQL.</title>
    <guid isPermaLink="false">https://anotherboringtechblog.com/?p=600</guid>
    <link>https://anotherboringtechblog.com/2026/09/mysql-keeps-restarting-on-its-own/</link>
    <description>The alert fires at 06:37 and by the time anyone opens a laptop the database is back. The application log holds a wall of Lost connection to MySQL server during query, uptime says 41 days, and the error log records a clean shutdown: no crash, no signal, no out-of-memory kill. The next morning it happens at 06:12, and the morning after that not at all.
On a default Ubuntu or Debian server, unattended-upgrades installs security updates on a daily systemd timer, and when one touches MySQL, dpkg stops mysqld, upgrades the data directory to match the new binary, and starts it again while the machine stays up. Nothing rebooted. The database was replaced underneath a host that never stopped running.
That is deliberate, and the part nobody signed up for is that “a service” includes the database. We put a client in front of MySQL on an Amazon Elastic Compute Cloud (EC2) instance and let the timer do what it does every morning.
Is this a MySQL problem?
No. The restart was not written by anyone at MySQL. It comes from a block in the Debian package’s install script, generated by the standard packaging tooling and marked as such in the file, and every Debian package carrying a daemon ships the same one. It starts the service on first install and restarts it on every upgrade after, because the packaging cannot tell a database from a print spooler.
The second mechanism is not MySQL-specific either. Ubuntu ships needrestart, wired into apt, which restarts any service whose libraries were replaced on disk. In one run an OpenSSL update arrived, MySQL was held back and never upgraded, and needrestart restarted it anyway, along with sshd and the journal. Postgres, Redis and nginx would have been on that list too.
What does unattended-upgrades actually do?
It is a Python program plus two systemd units that install updates without a human: refresh the package lists, take what is upgradable from origins it is allowed to touch, install with dpkg, and reboot afterwards if a package asked for one and the operator turned that on. Its whole policy is two small config files, which on a stock Ubuntu 24.04 image say run daily and treat the security pocket as fair game. The blacklist is empty, so there is no exception for databases. Nobody configured that; it is what the image ships.
Does the RHEL family do this too?
Partly, and the difference is the half that matters. We ran the same checks on a stock Amazon Linux 2023 image:





Ubuntu 24.04
Amazon Linux 2023




Automatic installer present
unattended-upgrades, installed
dnf-automatic, not installed


Its timer
enabled
all four disabled, even after you install it


Default when it runs
installs security updates
downloads, installs nothing


Restarts services after a library update
needrestart, installed and active
nothing equivalent


Package upgrade restarts the database
yes
yes




Every row was read off the image rather than from documentation.
The last row matters: the RPM restarts the daemon on upgrade exactly as the Debian package does, and a dnf upgrade of MariaDB stopped and started the database. What Ubuntu adds is not the restart, it is the schedule.
How long does it take the database away?

A systemctl restart mysql costs about four seconds. One automatic security upgrade costs 22.98 seconds, because dpkg does not restart MySQL, it replaces it: mysqld starts and stops three times in those 23 seconds, and two of the three are listening on nothing while the data directory is upgraded. The machine does sometimes reboot on its own, when a kernel update sets /var/run/reboot-required and Automatic-Reboot is on, which Ubuntu ships off: we turned it on and the client lost the database for 19.37 seconds, less than the package upgrade costs.
And 23 seconds is the floor, because our lab server runs the 128 MB default buffer pool with almost nothing in it. Stopping a real database means flushing every dirty page first. On a second instance with an 8 GB buffer pool, a 5.5 GB table and roughly 2.8 GB of pages dirty, an ordinary systemctl stop mysql took 1 minute 59.8 seconds, and nothing bounds it: the MySQL unit Ubuntu ships gives the stop no timeout at all. Scale that pool to the terabyte a real server has and this stops being a blip.
Why does it feel random?
Because it is scheduled to be. The timer runs at 06:00 with RandomizedDelaySec=60m, spreading the start across the following hour independently on every host, which is what the setting is for: it keeps a fleet off the mirrors at the same second. So the restart lands at a different minute every morning and on every machine, and the repetition that would make it obvious never forms. Persistent=true fires a missed run as soon as the machine comes back, which is how one lands ten minutes after a maintenance window rather than during it.
How do I tell which one happened?
First settle whether the machine rebooted, because the two have different fixes and the same application error. Compare uptime -s, when the kernel booted, against systemctl show -p ActiveEnterTimestamp --value mysql, when this mysqld started.
Then three logs and one line. /var/log/apt/history.log names what apt installed, the unattended-upgrades log says what it decided, and journalctl -u mysql shows systemd stopping and starting the service with a gap of tens of seconds where dpkg works. The MySQL error log settles it: MY-013381, the “Server upgrade from” line, appears only when MySQL finds a data directory from an older version and upgrades it, so a crash, an out-of-memory kill, an operator restart and a host reboot cannot produce it.
How do I disable or remove it?
My opinion: on a production database server, automatic installation of database packages should be off. Replacing a running mysqld is a maintenance action, and maintenance actions belong to whoever answers for the outage. On a web tier behind a load balancer, leave it on.
To disable it, set APT::Periodic::Unattended-Upgrade &quot;0&quot; in /etc/apt/apt.conf.d/20auto-upgrades, which is what dpkg-reconfigure -plow unattended-upgrades writes. The daily unit then runs and installs nothing. To stop the timer entirely, systemctl mask it rather than disable it: a disabled unit stays startable and a later upgrade can restore the symlink.
To remove it, apt-get purge unattended-upgrades, which takes the binary and both config files but keeps the logs. It does not take the timers: apt-daily.timer and apt-daily-upgrade.timer ship with apt itself and stay enabled, firing into a binary that is gone. We ran all of this with the upgrade pending and a client probing; MySQL never restarted.
Two caveats. needrestart runs on every apt transaction including yours, so on a purged host a hand-typed apt-get upgrade of OpenSSL still restarted MySQL and cost 4.10 seconds; excluding the database there takes a one-line override in /etc/needrestart/conf.d/. And this moves the outage to a moment you chose rather than removing the need for it.
Conclusion
A database that disappears for twenty seconds at night, on a machine whose uptime says weeks, is unattended-upgrades doing what it was configured to do on the day the machine was built, at a minute systemd picked at random. It is not a MySQL bug, and not really an Ubuntu bug either. What Ubuntu adds is that nobody had to ask, and the machine picks the minute. On a web tier that is a good trade. On a database it should be a decision a person makes, in a window they chose.
O post Something restarts my MySQL every morning. It isn’t MySQL. apareceu primeiro em Another Boring Tech Blog.</description>
    <content:encoded><![CDATA[<p>The alert fires at 06:37 and by the time anyone opens a laptop the database is back. The application log holds a wall of <code>Lost connection to MySQL server during query</code>, <code>uptime</code> says 41 days, and the error log records a clean shutdown: no crash, no signal, no out-of-memory kill. The next morning it happens at 06:12, and the morning after that not at all.</p>
<p>On a default Ubuntu or Debian server, <code>unattended-upgrades</code> installs security updates on a daily systemd timer, and when one touches MySQL, dpkg stops <code>mysqld</code>, upgrades the data directory to match the new binary, and starts it again while the machine stays up. Nothing rebooted. The database was replaced underneath a host that never stopped running.</p>
<p>That is deliberate, and the part nobody signed up for is that “a service” includes the database. We put a client in front of MySQL on an Amazon Elastic Compute Cloud (EC2) instance and let the timer do what it does every morning.</p>
<h2>Is this a MySQL problem?</h2>
<p>No. The restart was not written by anyone at MySQL. It comes from a block in the Debian package’s install script, generated by the standard packaging tooling and marked as such in the file, and every Debian package carrying a daemon ships the same one. It starts the service on first install and restarts it on every upgrade after, because the packaging cannot tell a database from a print spooler.</p>
<p>The second mechanism is not MySQL-specific either. Ubuntu ships <code>needrestart</code>, wired into apt, which restarts any service whose libraries were replaced on disk. In one run an OpenSSL update arrived, MySQL was held back and never upgraded, and <code>needrestart</code> restarted it anyway, along with <code>sshd</code> and the journal. Postgres, Redis and nginx would have been on that list too.</p>
<h2>What does unattended-upgrades actually do?</h2>
<p>It is a Python program plus two systemd units that install updates without a human: refresh the package lists, take what is upgradable from origins it is allowed to touch, install with dpkg, and reboot afterwards if a package asked for one and the operator turned that on. Its whole policy is two small config files, which on a stock Ubuntu 24.04 image say run daily and treat the security pocket as fair game. The blacklist is empty, so there is no exception for databases. Nobody configured that; it is what the image ships.</p>
<h2>Does the RHEL family do this too?</h2>
<p>Partly, and the difference is the half that matters. We ran the same checks on a stock Amazon Linux 2023 image:</p>
<div class="table-wrap">
<table>
<thead>
<tr>
<th></th>
<th>Ubuntu 24.04</th>
<th>Amazon Linux 2023</th>
</tr>
</thead>
<tbody>
<tr>
<td>Automatic installer present</td>
<td><code>unattended-upgrades</code>, installed</td>
<td><code>dnf-automatic</code>, <strong>not installed</strong></td>
</tr>
<tr>
<td>Its timer</td>
<td><strong>enabled</strong></td>
<td>all four <strong>disabled</strong>, even after you install it</td>
</tr>
<tr>
<td>Default when it runs</td>
<td>installs security updates</td>
<td>downloads, installs nothing</td>
</tr>
<tr>
<td>Restarts services after a library update</td>
<td><code>needrestart</code>, installed and active</td>
<td>nothing equivalent</td>
</tr>
<tr>
<td>Package upgrade restarts the database</td>
<td>yes</td>
<td>yes</td>
</tr>
</tbody>
</table>
</div>
<p class="note">Every row was read off the image rather than from documentation.</p>
<p>The last row matters: the RPM restarts the daemon on upgrade exactly as the Debian package does, and a <code>dnf upgrade</code> of MariaDB stopped and started the database. What Ubuntu adds is not the restart, it is the schedule.</p>
<h2>How long does it take the database away?</h2>
<figure><img decoding="async" src="data:image/png;base64,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" alt="Same server, five ways to lose it"></figure>
<p>A <code>systemctl restart mysql</code> costs about four seconds. One automatic security upgrade costs <strong>22.98 seconds</strong>, because dpkg does not restart MySQL, it replaces it: <code>mysqld</code> starts and stops three times in those 23 seconds, and two of the three are listening on nothing while the data directory is upgraded. The machine does sometimes reboot on its own, when a kernel update sets <code>/var/run/reboot-required</code> and <code>Automatic-Reboot</code> is on, which Ubuntu ships off: we turned it on and the client lost the database for <strong>19.37 seconds</strong>, less than the package upgrade costs.</p>
<p>And 23 seconds is the floor, because our lab server runs the 128 MB default buffer pool with almost nothing in it. Stopping a real database means flushing every dirty page first. On a second instance with an 8 GB buffer pool, a 5.5 GB table and roughly 2.8 GB of pages dirty, an ordinary <code>systemctl stop mysql</code> took <strong>1 minute 59.8 seconds</strong>, and nothing bounds it: the MySQL unit Ubuntu ships gives the stop no timeout at all. Scale that pool to the terabyte a real server has and this stops being a blip.</p>
<h2>Why does it feel random?</h2>
<p>Because it is scheduled to be. The timer runs at 06:00 with <code>RandomizedDelaySec=60m</code>, spreading the start across the following hour independently on every host, which is <a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html" target="_blank" rel="noopener">what the setting is for</a>: it keeps a fleet off the mirrors at the same second. So the restart lands at a different minute every morning and on every machine, and the repetition that would make it obvious never forms. <code>Persistent=true</code> fires a missed run as soon as the machine comes back, which is how one lands ten minutes after a maintenance window rather than during it.</p>
<h2>How do I tell which one happened?</h2>
<p>First settle whether the machine rebooted, because the two have different fixes and the same application error. Compare <code>uptime -s</code>, when the kernel booted, against <code>systemctl show -p ActiveEnterTimestamp --value mysql</code>, when this <code>mysqld</code> started.</p>
<p>Then three logs and one line. <code>/var/log/apt/history.log</code> names what apt installed, the <code>unattended-upgrades</code> log says what it decided, and <code>journalctl -u mysql</code> shows systemd stopping and starting the service with a gap of tens of seconds where dpkg works. The MySQL error log settles it: <code>MY-013381</code>, the “Server upgrade from” line, appears only when MySQL finds a data directory from an older version and upgrades it, so a crash, an out-of-memory kill, an operator restart and a host reboot cannot produce it.</p>
<h2>How do I disable or remove it?</h2>
<p>My opinion: on a production database server, automatic installation of database packages should be off. Replacing a running <code>mysqld</code> is a maintenance action, and maintenance actions belong to whoever answers for the outage. On a web tier behind a load balancer, leave it on.</p>
<p>To disable it, set <code>APT::Periodic::Unattended-Upgrade "0"</code> in <code>/etc/apt/apt.conf.d/20auto-upgrades</code>, which is what <code>dpkg-reconfigure -plow unattended-upgrades</code> writes. The daily unit then runs and installs nothing. To stop the timer entirely, <code>systemctl mask</code> it rather than disable it: a disabled unit stays startable and a later upgrade can restore the symlink.</p>
<p>To remove it, <code>apt-get purge unattended-upgrades</code>, which takes the binary and both config files but keeps the logs. It does not take the timers: <code>apt-daily.timer</code> and <code>apt-daily-upgrade.timer</code> ship with <code>apt</code> itself and stay enabled, firing into a binary that is gone. We ran all of this with the upgrade pending and a client probing; MySQL never restarted.</p>
<p>Two caveats. <code>needrestart</code> runs on every apt transaction including yours, so on a purged host a hand-typed <code>apt-get upgrade</code> of OpenSSL still restarted MySQL and cost <strong>4.10 seconds</strong>; excluding the database there takes a one-line override in <code>/etc/needrestart/conf.d/</code>. And this moves the outage to a moment you chose rather than removing the need for it.</p>
<h2>Conclusion</h2>
<p>A database that disappears for twenty seconds at night, on a machine whose uptime says weeks, is <code>unattended-upgrades</code> doing what it was configured to do on the day the machine was built, at a minute systemd picked at random. It is not a MySQL bug, and not really an Ubuntu bug either. What Ubuntu adds is that nobody had to ask, and the machine picks the minute. On a web tier that is a good trade. On a database it should be a decision a person makes, in a window they chose.</p>
<p>O post <a href="https://anotherboringtechblog.com/2026/09/mysql-keeps-restarting-on-its-own/">Something restarts my MySQL every morning. It isn’t MySQL.</a> apareceu primeiro em <a href="https://anotherboringtechblog.com/">Another Boring Tech Blog</a>.</p>]]></content:encoded>
    <pubDate>Sun, 06 Sep 2026 21:32:20 +0000</pubDate>
    <dc:creator>Vinicius Grippa</dc:creator>
    <category>MySQL</category>
  </item>

  <item>
    <title>Database branching explained: How it works and when to use it </title>
    <guid isPermaLink="false">https://www.devart.com/blog/?p=183152</guid>
    <link>https://www.devart.com/blog/database-branching-explained.html</link>
    <description>Your application code branches in seconds. Your database still gets copied, shared, and fought over. Here is how database branching closes that gap, where it breaks down, and what has to sit around it before it makes releases safer.
The post Database branching explained: How it works and when to use it  appeared first on Devart Blog.</description>
    <content:encoded><![CDATA[<p>Your application code branches in seconds. Your database still gets copied, shared, and fought over. Here is how database branching closes that gap, where it breaks down, and what has to sit around it before it makes releases safer.</p>
<p>The post <a href="https://www.devart.com/blog/database-branching-explained.html">Database branching explained: How it works and when to use it </a> appeared first on <a href="https://www.devart.com/blog">Devart Blog</a>.</p>]]></content:encoded>
    <pubDate>Sat, 05 Sep 2026 16:56:05 +0000</pubDate>
    <dc:creator>Alena Subotina</dc:creator>
    <category>MySQL Tools</category>
    <category>Oracle Tools</category>
    <category>PostgreSQL Tools</category>
    <category>SQL Server Tools</category>
    <category>database</category>
    <category>dbForge Edge</category>
  </item>

  <item>
    <title>Twitter Login Using Node and MySQL</title>
    <guid isPermaLink="false">http://codeforgeek.com/?p=380</guid>
    <link>https://codeforgeek.com/twitter-login-using-node/</link>
    <description>Twitter login with Node breaks every few years because the pieces move under it. I rebuilt this app on the current releases and fixed each place where the old code stops working, so you can follow it step by step and finish with a working sign-in. Live Demo Download Code Pick the right X auth […]</description>
    <pubDate>Sat, 05 Sep 2026 09:11:44 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Database</category>
    <category>Express tutorials</category>
    <category>Mysql</category>
    <category>Node Tutorials</category>
    <category>Tutorials</category>
  </item>

  <item>
    <title>MySQL 26.7 and the Power of Community Contribution</title>
    <guid isPermaLink="false">9000a47fc26ae3c147f67b3831afc085</guid>
    <link>https://blogs.oracle.com/mysql/mysql-26-7-and-the-power-of-community-contribution</link>
    <description>As we have discussed through the new MySQL Community Engagement initiative, we are sharing metrics that highlight how the community contributes to MySQL. These measures help us understand the growth of community participation, recognize the people helping improve the product, and identify where we can continue to strengthen the contributor experience. The MySQL community delivered […]</description>
    <pubDate>Fri, 04 Sep 2026 16:43:02 +0000</pubDate>
    <dc:creator>Oracle MySQL Group</dc:creator>
    <category>MySQL</category>
    <category>MySQL Community</category>
    <category>mysql</category>
    <category>MySQL 26.7</category>
    <category>mysqlcommunity</category>
  </item>

  <item>
    <title>150+ SQL Commands Explained With Examples (2026 Update)</title>
    <guid isPermaLink="false">https://codeforgeek.com/?p=36810</guid>
    <link>https://codeforgeek.com/sql-commands-explained-with-examples/</link>
    <description>In this guide, we explain 150+ SQL commands in simple words, covering everything from basic queries to advanced functions for 2026. We cover almost every SQL command that exists in one single place, so you never have to go search for anything anywhere else. If you master these 150 commands, you will become an SQL […]</description>
    <pubDate>Fri, 04 Sep 2026 07:47:10 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Mysql</category>
  </item>

  <item>
    <title>OAuth2 and JWT Logins for MySQL</title>
    <guid isPermaLink="false">6a9861fefc02380001ac37be</guid>
    <link>https://villagesql.com/blog/oauth2/</link>
    <description>An engineer leaves your company. You disable their single sign-on account, and their access to the wiki, the cloud console, and the CI system goes with it. Typically, access to database resources doesn't follow such a simple plan. Database user and role maintenance too often happens within the database, oblivious to single sign-on. User accounts carry a password which has to be managed and rotated (and the password is probably already sprawled out across shell history and a shared password manager).An easier way to manage this is to bring the identity-provider model to the database. This is what the VillageSQL Server vsql-oauth2 extension does. It adds an authentication method that accepts an OAuth2/OpenID Connect token (a JWT) from your identity provider in place of a password. Your identity provider decides who can log in, MySQL privileges decide what they can do inside, and disabling someone stops them getting another token. The token already in hand works until it expires, so your provider's token lifetime dictates the access window.VillageSQL is the innovation platform for MySQL that adds an extension framework (similar to PostgreSQL's extension framework) to enable permissionless innovation. Instead of waiting for a feature to be implemented in a future version of MySQL in a few years, new functionality can be dynamically added to the version of MySQL you run today. VillageSQL Server supports MySQL 8.4, 9.7, and Percona Server 8.4. The vsql-oauth2 extension is an example of what is meant by permissionless innovation.The rest of this post shows how the vsql-oauth2 extension works. The examples use Microsoft Entra ID, because Entra puts readable role names directly into the token. We start with the simple case: install the extension, point it at Entra's signing keys, and log in to the database with a token instead of a password. We then add role mapping, so a person the database has never seen before can log in for the first time and come away with an account and the roles their identity provider assigned them.


If you would rather stop reading here and have your AI agent demonstrate this for you with a mock identity provider, open this dropdown and copy the prompt into your preferred AI coding tool.

Set up a working demo of passwordless MySQL logins on my machine, using the
vsql-oauth2 extension for VillageSQL. Act as my own identity provider so no real
IdP is needed. Work only against a local throwaway server — if the only
VillageSQL or MySQL server you find looks like something I depend on, stop and
ask me before touching it.

Do all of this yourself, and show me the real output of each step:

1. Find a VillageSQL server, or install one. The install script needs a method:
   `curl -fsSL https://install.villagesql.com | VSQL_CODEBASE=mysql-8.4 INSTALL_METHOD=prebuilt bash`.
   Start it with `--vsql_allow_preview_extensions=ON`; on a server already
   running, `SET PERSIST vsql_allow_preview_extensions=ON` takes effect at once.
   Confirm with `SELECT VERSION()` before continuing.

2. Install the extension: `INSTALL EXTENSION vsql_oauth2;`. It ships with the
   server, so there is nothing to download. Read
   `INFORMATION_SCHEMA.EXTENSION_REGISTRATION` and
   `SHOW GLOBAL VARIABLES LIKE 'vsql_oauth2.%'` and tell me what settings exist.

3. Stand in for the identity provider. Generate an RSA keypair with openssl, and
   write a small script that mints signed RS256 JWTs from a claims payload, each
   header carrying a `kid`. Use openssl only — do not install a JWT library.
   Then publish the public key the way a real provider does: serve a JWKS
   document over HTTP on localhost, holding the key as a JWK with `kty`, `n`,
   `e` and that same `kid`. Let the operating system choose the port rather than
   picking one yourself, and tell me the URL.

4. Do the basic case. Set `issuer`, `audience` and `username_claim`, and point
   `jwks_url` at the JWKS URL from step 3. Create an account bound to
   `vsql_oauth2`, create the account it maps onto, and grant the proxy. Then log
   in with a token in place of the password and show me
   `SELECT CURRENT_USER(), @@external_user;`. Show me your JWKS server's own log
   as evidence that the database really fetched the keys.

5. Do the joiner case. Turn on `roles_claim`, `roles_filter`,
   `roles_transform_pattern`, `roles_transform_replacement`, `auto_create` and
   `auto_grant`. Create two roles named in the rewritten form the transform
   produces, then log in as a user who has never touched this database with a
   token carrying a roles claim. Show me that the account and the role grant both
   appeared, and that the role is active for the session.

6. Try to break it. Take a token you have just shown works and change exactly
   one thing at a time: expire it, alter the issuer, alter the audience, name a
   `kid` your JWKS document does not carry, strip the signature with
   `alg: none`, and re-sign it with HMAC using the public key as the shared
   secret. Show me all six refused. Every refusal prints the same message, so
   for each one name the single change you made, and log in with the unchanged
   token in the same run to prove the refusal came from that change.

Then give me a table of what you ran and what came back, tell me anything that
did not behave the way this asked, and drop every user, role and setting you
created, stop the JWKS server, and leave my server back where it started.



Log in without a database password
Below is the server-side setup for the basic case, using Microsoft Entra ID:
INSTALL EXTENSION vsql_oauth2;

SET GLOBAL vsql_oauth2.issuer   = 'https://login.microsoftonline.com/&amp;lt;tenant-guid&amp;gt;/v2.0';
SET GLOBAL vsql_oauth2.jwks_url = 'https://login.microsoftonline.com/&amp;lt;tenant-guid&amp;gt;/discovery/v2.0/keys';
SET GLOBAL vsql_oauth2.audience = '&amp;lt;database-app-client-id&amp;gt;';
SET GLOBAL vsql_oauth2.username_claim = 'preferred_username';

CREATE USER oauth_user IDENTIFIED WITH vsql_oauth2;
CREATE USER 'dana@myco.example';
GRANT SELECT ON *.* TO 'dana@myco.example';
GRANT PROXY ON 'dana@myco.example' TO oauth_user;

jwks_url is Entra's key endpoint. The extension fetches the signing keys from it and refreshes them hourly by default, so Entra can rotate its keys without anyone touching the database. The public_key setting is the alternative: it pins one key you paste in yourself, which suits a self-signed test and stops working at the provider's next rotation. Set both and jwks_url wins.
Point audience at the app registration that stands for the database, and send Entra's access token for that app rather than the id_token. Only the access token carries App Roles under the readable names the next section filters on. The README's provider settings cover the app registrations Entra needs and the values for other providers.
From there, the standard mysql client logs in with a token where the password would normally go. Passing it through MYSQL_PWD keeps it out of the process list:
$ MYSQL_PWD='&amp;lt;the JWT&amp;gt;' mysql --enable-cleartext-plugin --user=oauth_user \
    -e &quot;SELECT CURRENT_USER(), @@external_user;&quot;
+---------------------+-------------------+
| CURRENT_USER()      | @@external_user   |
+---------------------+-------------------+
| dana@myco.example@% | dana@myco.example |
+---------------------+-------------------+

The credential is a short-lived token from your identity provider, so there is no password to store or rotate. Where the token comes from depends on who is connecting, e.g., a person at a shell, a CI job, or an application. The extension takes the same token in all cases. Obtaining it is ordinary OAuth for your provider.
The vsql-oauth2 README has the commands for all three. It also covers the vsql_oauth_client plugin, which fetches the token itself so it never touches the command line. However you send it, the token travels in cleartext at the protocol level, so the connection has to run over TLS.
You connect as oauth_user, which is bound to the extension and holds no privileges of its own. The username_claim setting says which claim names the account to run as, and GRANT PROXY is what allows the switch. Entra's sub is an opaque identifier, so this example reads preferred_username and gets Dana's sign-in name. The session then has dana@myco.example's privileges. @@external_user reports the identity that arrived in the token, so the audit trail keeps it even though the session runs as another account.
The token has to be signed with one of your provider's published keys for any of this to happen, and the extension accepts RSA and ECDSA signatures only. An unsigned token is refused before any signature check runs, and so is one that switches to a symmetric algorithm hoping the server will reuse your public key as a shared secret.
Easy database access management
The basic case maps a token to an account you created ahead of time. The extension can go further and take its cues from the App Roles Entra assigned the person signing in. You tell it which claim to read and how to rewrite the names, then create the roles those App Roles map onto:
SET GLOBAL vsql_oauth2.roles_claim = 'roles';
SET GLOBAL vsql_oauth2.roles_filter = 'mysql-grp-.*';
SET GLOBAL vsql_oauth2.roles_transform_pattern = '-';
SET GLOBAL vsql_oauth2.roles_transform_replacement = '_';
SET GLOBAL vsql_oauth2.auto_create = ON;
SET GLOBAL vsql_oauth2.auto_grant = ON;

CREATE ROLE mysql_grp_dba;

The two transform settings rewrite each matched App Role before it becomes a role name, turning mysql-grp-dba into mysql_grp_dba. Now someone who has never touched this database logs in with a token that says preferred_username: alice@myco.example, roles: [mysql-grp-dba]:
$ MYSQL_PWD='&amp;lt;her JWT&amp;gt;' mysql --enable-cleartext-plugin --user='alice@myco.example' \
    -e &quot;SELECT CURRENT_USER(); SELECT CURRENT_ROLE();&quot;
+----------------------+
| CURRENT_USER()       |
+----------------------+
| alice@myco.example@% |
+----------------------+
+---------------------+
| CURRENT_ROLE()      |
+---------------------+
| `mysql_grp_dba`@`%` |
+---------------------+

One login created the account, granted the role her App Role entitles her to, and activated it for the session. An auto-created account runs as itself, so this path needs no proxy grant. The DBA never saw a ticket and never ran a CREATE USER. The DBA's job moves up a level: grant each role its privileges once, and let the identity provider say who holds the App Role. When an App Role later disappears from someone's token, that role stops activating at their logins, but the granted membership stays until a human revokes it.
Alice had no account, so auto_create did all of it. auto_grant covers the other case, someone who already has an account, and it grants the roles their token claims each time they log in. Both default to off and work independently, so you can turn on either one without the other. If you leave them off, tokens only ever activate roles you granted by hand, and unknown users stay unknown.
Try it out
Please try out vsql-oauth2 against your identity provider, tell us how it goes, especially which claim layouts your tokens carry. If you are not on Entra, the same settings apply — your provider's discovery document (/.well-known/openid-configuration) gives the issuer and jwks_url values. We would love to hear from you on Discord or leave an issue on vsql-oauth2.
To get started with VillageSQL Server, go to villagesql.com.</description>
    <content:encoded><![CDATA[<img src="https://storage.ghost.io/c/db/c7/dbc78ca4-dcfe-468b-b2fa-030396d2a98e/content/images/2026/09/Gemini_Generated_Image_1axtuk1axtuk1axt.jpeg" alt="OAuth2 and JWT Logins for MySQL"><p>An engineer leaves your company. You disable their single sign-on account, and their access to the wiki, the cloud console, and the CI system goes with it. Typically, access to database resources doesn't follow such a simple plan. Database user and role maintenance too often happens within the database, oblivious to single sign-on. User accounts carry a password which has to be managed and rotated (and the password is probably already sprawled out across shell history and a shared password manager).</p><p>An easier way to manage this is to bring the identity-provider model to the database. This is what the <a href="https://github.com/villagesql/vsql-oauth2"><u>VillageSQL Server vsql-oauth2</u></a> extension does. It adds an authentication method that accepts an OAuth2/OpenID Connect token (a JWT) from your identity provider in place of a password. Your identity provider decides who can log in, MySQL privileges decide what they can do inside, and disabling someone stops them getting another token. The token already in hand works until it expires, so your provider's token lifetime dictates the access window.</p><p>VillageSQL is the innovation platform for MySQL that adds an extension framework (similar to PostgreSQL's extension framework) to enable permissionless innovation. Instead of waiting for a feature to be implemented in a future version of MySQL in a few years, new functionality can be dynamically added to the version of MySQL you run today. VillageSQL Server supports MySQL 8.4, 9.7, and Percona Server 8.4. The vsql-oauth2 extension is an example of what is meant by permissionless innovation.</p><p>The rest of this post shows how the vsql-oauth2 extension works. The examples use <a href="https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id" rel="noreferrer">Microsoft Entra ID</a>, because Entra puts readable role names directly into the token. We start with the simple case: install the extension, point it at Entra's signing keys, and log in to the database with a token instead of a password. We then add role mapping, so a person the database has never seen before can log in for the first time and come away with an account and the roles their identity provider assigned them.</p><hr>
<!--kg-card-begin: html-->
<details>
<summary><strong>If you would rather stop reading here and have your AI agent demonstrate this for you with a mock identity provider, open this dropdown and copy the prompt into your preferred AI coding tool.</strong></summary>
<pre>
Set up a working demo of passwordless MySQL logins on my machine, using the
vsql-oauth2 extension for VillageSQL. Act as my own identity provider so no real
IdP is needed. Work only against a local throwaway server — if the only
VillageSQL or MySQL server you find looks like something I depend on, stop and
ask me before touching it.

Do all of this yourself, and show me the real output of each step:

1. Find a VillageSQL server, or install one. The install script needs a method:
   `curl -fsSL https://install.villagesql.com | VSQL_CODEBASE=mysql-8.4 INSTALL_METHOD=prebuilt bash`.
   Start it with `--vsql_allow_preview_extensions=ON`; on a server already
   running, `SET PERSIST vsql_allow_preview_extensions=ON` takes effect at once.
   Confirm with `SELECT VERSION()` before continuing.

2. Install the extension: `INSTALL EXTENSION vsql_oauth2;`. It ships with the
   server, so there is nothing to download. Read
   `INFORMATION_SCHEMA.EXTENSION_REGISTRATION` and
   `SHOW GLOBAL VARIABLES LIKE 'vsql_oauth2.%'` and tell me what settings exist.

3. Stand in for the identity provider. Generate an RSA keypair with openssl, and
   write a small script that mints signed RS256 JWTs from a claims payload, each
   header carrying a `kid`. Use openssl only — do not install a JWT library.
   Then publish the public key the way a real provider does: serve a JWKS
   document over HTTP on localhost, holding the key as a JWK with `kty`, `n`,
   `e` and that same `kid`. Let the operating system choose the port rather than
   picking one yourself, and tell me the URL.

4. Do the basic case. Set `issuer`, `audience` and `username_claim`, and point
   `jwks_url` at the JWKS URL from step 3. Create an account bound to
   `vsql_oauth2`, create the account it maps onto, and grant the proxy. Then log
   in with a token in place of the password and show me
   `SELECT CURRENT_USER(), @@external_user;`. Show me your JWKS server's own log
   as evidence that the database really fetched the keys.

5. Do the joiner case. Turn on `roles_claim`, `roles_filter`,
   `roles_transform_pattern`, `roles_transform_replacement`, `auto_create` and
   `auto_grant`. Create two roles named in the rewritten form the transform
   produces, then log in as a user who has never touched this database with a
   token carrying a roles claim. Show me that the account and the role grant both
   appeared, and that the role is active for the session.

6. Try to break it. Take a token you have just shown works and change exactly
   one thing at a time: expire it, alter the issuer, alter the audience, name a
   `kid` your JWKS document does not carry, strip the signature with
   `alg: none`, and re-sign it with HMAC using the public key as the shared
   secret. Show me all six refused. Every refusal prints the same message, so
   for each one name the single change you made, and log in with the unchanged
   token in the same run to prove the refusal came from that change.

Then give me a table of what you ran and what came back, tell me anything that
did not behave the way this asked, and drop every user, role and setting you
created, stop the JWKS server, and leave my server back where it started.
</pre>
</details>
<!--kg-card-end: html-->
<hr><h2>Log in without a database password</h2>
<p>Below is the server-side setup for the basic case, using Microsoft Entra ID:</p>
<pre><code class="language-sql">INSTALL EXTENSION vsql_oauth2;

SET GLOBAL vsql_oauth2.issuer   = 'https://login.microsoftonline.com/&lt;tenant-guid&gt;/v2.0';
SET GLOBAL vsql_oauth2.jwks_url = 'https://login.microsoftonline.com/&lt;tenant-guid&gt;/discovery/v2.0/keys';
SET GLOBAL vsql_oauth2.audience = '&lt;database-app-client-id&gt;';
SET GLOBAL vsql_oauth2.username_claim = 'preferred_username';

CREATE USER oauth_user IDENTIFIED WITH vsql_oauth2;
CREATE USER 'dana@myco.example';
GRANT SELECT ON *.* TO 'dana@myco.example';
GRANT PROXY ON 'dana@myco.example' TO oauth_user;
</code></pre>
<p><code>jwks_url</code> is Entra's key endpoint. The extension fetches the signing keys from it and refreshes them hourly by default, so Entra can rotate its keys without anyone touching the database. The <code>public_key</code> setting is the alternative: it pins one key you paste in yourself, which suits a self-signed test and stops working at the provider's next rotation. Set both and <code>jwks_url</code> wins.</p>
<p>Point <code>audience</code> at the app registration that stands for the database, and send Entra's access token for that app rather than the <code>id_token</code>. Only the access token carries App Roles under the readable names the next section filters on. The <a href="https://github.com/villagesql/vsql-oauth2/blob/main/README.md#provider-settings">README's provider settings</a> cover the app registrations Entra needs and the values for other providers.</p>
<p>From there, the standard <code>mysql</code> client logs in with a token where the password would normally go. Passing it through <code>MYSQL_PWD</code> keeps it out of the process list:</p>
<pre><code>$ MYSQL_PWD='&lt;the JWT&gt;' mysql --enable-cleartext-plugin --user=oauth_user \
    -e "SELECT CURRENT_USER(), @@external_user;"
+---------------------+-------------------+
| CURRENT_USER()      | @@external_user   |
+---------------------+-------------------+
| dana@myco.example@% | dana@myco.example |
+---------------------+-------------------+
</code></pre>
<p>The credential is a short-lived token from your identity provider, so there is no password to store or rotate. Where the token comes from depends on who is connecting, e.g., a person at a shell, a CI job, or an application. The extension takes the same token in all cases. Obtaining it is ordinary OAuth for your provider.</p>
<p>The <a href="https://github.com/villagesql/vsql-oauth2/blob/main/README.md">vsql-oauth2 README</a> has the commands for all three. It also covers the <code>vsql_oauth_client</code> plugin, which fetches the token itself so it never touches the command line. However you send it, the token travels in cleartext at the protocol level, so the connection has to run over TLS.</p>
<p>You connect as <code>oauth_user</code>, which is bound to the extension and holds no privileges of its own. The <code>username_claim</code> setting says which claim names the account to run as, and <code>GRANT PROXY</code> is what allows the switch. Entra's <code>sub</code> is an opaque identifier, so this example reads <code>preferred_username</code> and gets Dana's sign-in name. The session then has <code>dana@myco.example</code>'s privileges. <code>@@external_user</code> reports the identity that arrived in the token, so the audit trail keeps it even though the session runs as another account.</p>
<p>The token has to be signed with one of your provider's published keys for any of this to happen, and the extension accepts RSA and ECDSA signatures only. An unsigned token is refused before any signature check runs, and so is one that switches to a symmetric algorithm hoping the server will reuse your public key as a shared secret.</p>
<h2>Easy database access management</h2>
<p>The basic case maps a token to an account you created ahead of time. The extension can go further and take its cues from the App Roles Entra assigned the person signing in. You tell it which claim to read and how to rewrite the names, then create the roles those App Roles map onto:</p>
<pre><code class="language-sql">SET GLOBAL vsql_oauth2.roles_claim = 'roles';
SET GLOBAL vsql_oauth2.roles_filter = 'mysql-grp-.*';
SET GLOBAL vsql_oauth2.roles_transform_pattern = '-';
SET GLOBAL vsql_oauth2.roles_transform_replacement = '_';
SET GLOBAL vsql_oauth2.auto_create = ON;
SET GLOBAL vsql_oauth2.auto_grant = ON;

CREATE ROLE mysql_grp_dba;
</code></pre>
<p>The two transform settings rewrite each matched App Role before it becomes a role name, turning <code>mysql-grp-dba</code> into <code>mysql_grp_dba</code>. Now someone who has never touched this database logs in with a token that says <code>preferred_username: alice@myco.example, roles: [mysql-grp-dba]</code>:</p>
<pre><code>$ MYSQL_PWD='&lt;her JWT&gt;' mysql --enable-cleartext-plugin --user='alice@myco.example' \
    -e "SELECT CURRENT_USER(); SELECT CURRENT_ROLE();"
+----------------------+
| CURRENT_USER()       |
+----------------------+
| alice@myco.example@% |
+----------------------+
+---------------------+
| CURRENT_ROLE()      |
+---------------------+
| `mysql_grp_dba`@`%` |
+---------------------+
</code></pre>
<p>One login created the account, granted the role her App Role entitles her to, and activated it for the session. An auto-created account runs as itself, so this path needs no proxy grant. The DBA never saw a ticket and never ran a <code>CREATE USER</code>. The DBA's job moves up a level: grant each role its privileges once, and let the identity provider say who holds the App Role. When an App Role later disappears from someone's token, that role stops activating at their logins, but the granted membership stays until a human revokes it.</p>
<p>Alice had no account, so <code>auto_create</code> did all of it. <code>auto_grant</code> covers the other case, someone who already has an account, and it grants the roles their token claims each time they log in. Both default to off and work independently, so you can turn on either one without the other. If you leave them off, tokens only ever activate roles you granted by hand, and unknown users stay unknown.</p>
<h2>Try it out</h2>
<p>Please try out vsql-oauth2 against your identity provider, tell us how it goes, especially which claim layouts your tokens carry. If you are not on Entra, the same settings apply — your provider's discovery document (<code>/.well-known/openid-configuration</code>) gives the <code>issuer</code> and <code>jwks_url</code> values. We would love to hear from you on <a href="https://discord.gg/KSr6whd3Fr">Discord</a> or leave an issue on <a href="https://github.com/villagesql/vsql-oauth2">vsql-oauth2</a>.</p>
<p>To get started with VillageSQL Server, go to <a href="https://villagesql.com/">villagesql.com</a>.</p>]]></content:encoded>
    <pubDate>Thu, 03 Sep 2026 17:37:36 +0000</pubDate>
    <dc:creator>VillageSQL</dc:creator>
    <category>Extension</category>
    <category>VillageSQL</category>
    <category>Open Source</category>
    <category>MySQL</category>
    <category>oauth</category>
  </item>

  <item>
    <title>10 Simple Steps to Solve SQL Problems [2026]</title>
    <guid isPermaLink="false">https://codeforgeek.com/?p=37051</guid>
    <link>https://codeforgeek.com/solve-sql-problems/</link>
    <description>SQL problems become manageable when you turn the prompt into decisions about rows, columns, grouping, and result order, and I ran the worked example below with Python 3.11.16 and SQLite 3.53.1 so you can inspect how each clause changes the result. Start with the requested result Before writing SQL, mark the rows the prompt should […]</description>
    <pubDate>Thu, 03 Sep 2026 09:16:08 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Mysql</category>
  </item>

  <item>
    <title>OpenID Connect Authentication for MySQL, Now Fully Open Source</title>
    <guid isPermaLink="false">https://www.percona.com/?p=52532</guid>
    <link>https://www.percona.com/blog/oidc-authentication-for-percona-mysql/</link>
    <description>Percona Server for MySQL now ships with a fully open source OpenID Connect (OIDC) authentication plugin, available starting with Percona Server for MySQL 8.4.11-11 and 9.7.2-2 (not yet released as of this writing). It allows a MySQL account to authenticate against any standards-compliant Identity Provider (IdP) instead of relying on a locally stored password, closing the gap with MySQL Enterprise Edition, which has offered OIDC authentication since MySQL 9.1 and, in several respects, going beyond it.
Oracle offers the same category of functionality, but its server-side plugin is part of the paid MySQL Enterprise Edition. Percona’s implementation is open source and adds three capabilities the Enterprise plugin does not provide: automatic signing-key synchronization from a JWKS endpoint, IdP group-to-role mapping, and proxy-user support. This article explains how the plugin works and why those differences matter in practice.
What OpenID Connect Brings to MySQL Authentication
OpenID Connect is an identity layer built on top of the OAuth 2.0 authorization framework [5]. Whereas OAuth 2.0 governs delegated access to resources, OIDC adds a standardized way for a client to establish who a user is. After a user signs in to an Identity Provider, the IdP issues a signed JSON Web Token (JWT), called an ID token, that carries the user’s identity and attributes in a verifiable, tamper-evident form.
Using that model for MySQL authentication brings several practical advantages over password-based accounts:

Alignment with single sign-on. Users authenticate once with their IdP and can reuse that session context across OIDC-aware applications, including databases. User lifecycle and password management remain centralized.
No long-lived secrets on the wire. ID tokens are short-lived and cryptographically signed, so there is no static password to steal, rotate, or accidentally commit to a configuration file.
Support for hybrid deployments. Organizations that run MySQL on-premises while hosting applications in the cloud can still authenticate through the same identity plane on both sides.
Broad interoperability. Because OpenID Connect is a widely adopted standard, the plugin can work with any compliant provider, including Keycloak, Okta, Microsoft Entra ID, and Google Identity.

None of that is unique to Percona; Oracle makes a similar value proposition for the Enterprise plugin. The real difference lies in how much operational burden the plugin removes from the administrator, which becomes clear in the next sections.
How OpenID Connect Authentication Works

Once the plugin and its configuration are in place, the authentication path is the same regardless of which IdP issued the token:

The user authenticates to the IdP and receives a signed ID token.
The token is written to a local file that only the client operating system account can read.
The MySQL client uses an option that causes the client-side OIDC plugin to load and read the token from the file. The token is sent to the server as part of the authentication handshake.
The server validates the secure channel and decodes the token. It then verifies the token signature using the selected IdP’s public key, checks the expiration time, and validates the configured claims.
The server resolves the final identity as either a personal account or a group-based proxy target. The plugin may also return roles mapped from the user’s group membership.

Configuring Trusted Providers and Letting the Plugin Manage the Keys
Identity Providers rotate their signing keys periodically as a basic security measure. If a key is ever compromised, limiting its lifetime reduces the potential impact, and regular rotation also lowers the long-term value of any one key as a target. In practice, rotation is gradual: a new key is published and accepted before it starts signing tokens, and an old key remains valid for a period after it stops signing so that tokens already in flight can still be verified.
Public keys are exposed through the standard JWKS (JSON Web Key Set) endpoint, which applications can use to verify tokens issued by the IdP [6].
The Percona OpenID Connect authentication plugin can download public keys from a configured JWKS endpoint when the plugin is loaded, typically during installation and server startup, and store them in a cache. It also provides a User Defined Function (UDF) that can refresh the cache on demand or periodically through the Event Scheduler.
By contrast, Oracle’s counterpart plugin requires signing keys to be configured statically through the authentication_openid_connect_configuration server variable, supplied either as an inline JSON string or as a path to a JSON file. There is no retrieval or refresh from the JWKS endpoint, so keeping keys current after each rotation remains a manual task for the administrator. In the window just after a rotation, tokens signed with the previous key are still valid but cannot be verified until the configuration is updated. Percona’s plugin supports static key configuration as well, but that mode is better suited to testing or temporary setups than to production.
Example
Using the feature requires two simple steps. First, JWKS endpoint URL must be set in the plugin’s configuration. For example, the below configuration defines IdP named as example-keycloak (pay attention to jwks-url element):{
  &quot;example-keycloak&quot;: {
  &quot;issuer-name&quot;: &quot;https://keycloak.example.com/realms/master&quot;,
  &quot;jwks-url&quot;: &quot;https://keycloak.example.com/realms/master/protocol/openid-connect/certs&quot;,
  &quot;audiences&quot;: [ &quot;mysql-oidc&quot; ]
  }
}The second step is ensuring the MySQL event scheduler is running and creating an event updating the keys. For example, to enable updating the keys for example-keycloak every hour run from MySQL client:CREATE EVENT update_oidc_keys
  ON SCHEDULE EVERY 1 HOUR
  DO SELECT update_jwks(&quot;example-keycloak&quot;);
Benefits of Using IdP Groups
This is where Percona’s plugin diverges most clearly from the Enterprise implementation.
Groups are managed by the corporate Identity Provider and group membership may be carried by ID tokens. OIDC does not define a standard claim for that, but most IdP implementations allow adding a group claim to the tokens. The Percona’s plugin allows the administrator to configure the group claim name so that it matches the token format used by the chosen IdP.
There are two practical ways to take advantage of this feature:  group-to-role mapping and proxy users.
Group-to-Role Mapping
Membership in a group can automatically translate into MySQL roles and therefore privileges across multiple MySQL servers at the same time. On a single server, the flow looks like this:

The administrator creates roles and grants them privileges.
The administrator defines the IdP group-to-MySQL role mapping in the plugin configuration file.
When the user connects, the plugin returns the roles that match the user’s groups, and the server automatically grants those roles to the user.
The user can activate any granted role and exercise the privileges assigned to it.

Please note, that group-to-role mapping still requires an account created for each user, but automates managing user privileges.
Example
To create roles and grant them some privileges one may run:CREATE ROLE accounting;
GRANT ALL PRIVILEGES ON accounting_database.* TO accounting;
CREATE ROLE sales;
GRANT ALL PRIVILEGES ON sales_database.* TO sales;Then, to to define the mapping add to IDP configuration:&quot;group-claim&quot;: &quot;groups&quot;,
&quot;group-role&quot;: [
  { &quot;/accounting&quot;: &quot;accounting&quot; },
  { &quot;/marketing&quot;: &quot;marketing&quot; }
]Any user connecting with an ID token containing claim “groups”:[“/accounting”] will be granted with role accounting and effectively obtain access to accounting_database and so on.
Proxy Users
The proxy capability in MySQL allows an authentication plugin to request that the connecting external user be logged in as a different MySQL user. In this model, the external identity is the proxy user and the mapped MySQL account is the proxied user. The purpose is to let multiple users share accounts with the same privilege set, avoiding the need to create a separate personal database account for every individual.
This feature must be supported by the authentication plugin, whose job is to choose the proxied user according to the specifics of the authentication method. In the Percona OIDC plugin, that selection is based on the group claim in the token and works as follows:

The administrator creates a proxy user identified by the OIDC plugin. This can be either a single anonymous account (”@”) without a specific group name, referred to as anonymous proxying, or multiple group-related accounts, referred to as named group proxying.
The administrator creates proxied users for each group. These accounts should not use a login plugin, so nobody can connect to them directly. The username must match the group name.
The administrator grants the PROXY privilege for each proxy user on all related proxied users.
When a user connects, in the anonymous proxying case the plugin returns the user’s first group as the proxied username. In the named group proxying case, the plugin checks whether the user belongs to the group and returns that group as the proxied username.
The server verifies that the requested proxied account exists and that the proxy user has the required PROXY privilege on it. If both checks succeed, the session runs with the proxied account’s privileges.

Other Features
Supported signing algorithms include RSASSA-PKCS1-v1_5, RSASSA-PSS, and ECDSA with SHA-256, SHA-384, and SHA-512 hashing functions.
The Percona approach uses the client-side OpenID Connect plugin from upstream MySQL, which ensures compatibility with the standard Oracle client.
Both client-side and server-side OpenID Connect plugins ensure that the token is sent via a secure channel. Accepted protocols are TCP protected by TLS, Unix sockets, and shared memory.
What OpenID Connect Authentication Does Not Do
There are some limits worth knowing.
The first comes from MySQL’s authentication design: any authentication plugin is used at connection time only. In the case of OIDC, the token is validated when the user connects, and a session that stays open may outlive the ID token that opened it. There is no out-of-the-box mechanism to force re-authentication after some time (except for idle connection timeout).
A similar situation applies to group-role mapping. The roles tied to the user’s groups in the ID token are granted or revoked at connection time. As a result, if a user is added to or removed from an IdP group, they must reconnect to Percona Server for the change to be reflected in their granted roles.
The proxying mechanism uses group membership claim instead of the token’s subject, so any token signed by a configured IdP that carries the required group is accepted. Group membership is your trust boundary in those modes, so treat it that way.
The current proxying implementation assumes the proxied user’s name matches the group name. This can be a problem when a group name isn’t a valid MySQL username (for example, it’s too long or contains disallowed characters), or when multiple groups need to map to a single account. We plan to add group-to-proxied-account mapping in future releases to address this.
The client-side plugin doesn’t verify the ID token (for example, check whether it has expired) before connecting, and the server doesn’t report the reason for access being denied (for security reasons). A good practice is to obtain a fresh token before connecting.
Conclusion
Functionally, Percona’s OpenID Connect plugin covers the same core ground as the counterpart in MySQL Enterprise Edition: signed ID tokens, claim validation, subject matching, and secure-transport enforcement.
It goes further in several important areas:

It is open source.
Keys can stay current automatically through JWKS synchronization.
Group-to-role mapping allows IdP group membership to drive MySQL role grants for the lifetime of the session.
Proxy-user support allows many IdP identities to share a smaller set of MySQL accounts.

Our OIDC implementation is suitable for real-world identity operations at scale. It can automatically map identities and groups managed by an IdP to database users and roles, and synchronize cryptographic keys.
References

Percona Server for MySQL documentation: OpenID Connect authentication.
Percona Server for MySQL documentation: Get started with OpenID Connect authentication.
MySQL 9.7 Reference Manual: OpenID Connect Pluggable Authentication.
MySQL 9.7 Reference Manual: Proxy Users.
OpenID Foundation: How OpenID Connect Works
auth0 Docs: JSON Web Key Sets.


Written by Michal Jankowski. Reviewed by Dennis Kittrell and Oleksiy Lukin.
Percona® is a registered trademark of Percona LLC. MySQL® is a registered trademark of Oracle Corporation.

The post OpenID Connect Authentication for MySQL, Now Fully Open Source appeared first on Percona.</description>
    <content:encoded><![CDATA[<p>Percona Server for MySQL now ships with a fully open source OpenID Connect (OIDC) authentication plugin, available starting with <strong>Percona Server for MySQL 8.4.11-11</strong> and <strong>9.7.2-2</strong> (not yet released as of this writing). It allows a MySQL account to authenticate against any standards-compliant Identity Provider (IdP) instead of relying on a locally stored password, closing the gap with MySQL Enterprise Edition, which has offered OIDC authentication since MySQL 9.1 and, in several respects, going beyond it.</p>
<p>Oracle offers the same category of functionality, but its server-side plugin is part of the paid MySQL Enterprise Edition. Percona’s implementation is open source and adds three capabilities the Enterprise plugin does not provide: automatic signing-key synchronization from a JWKS endpoint, IdP group-to-role mapping, and proxy-user support. This article explains how the plugin works and why those differences matter in practice.</p>
<h2>What OpenID Connect Brings to MySQL Authentication</h2>
<p>OpenID Connect is an identity layer built on top of the OAuth 2.0 authorization framework <a href="https://www.percona.com/blog/oidc-authentication-for-percona-mysql/#oidc">[5]</a>. Whereas OAuth 2.0 governs delegated access to resources, OIDC adds a standardized way for a client to establish who a user is. After a user signs in to an Identity Provider, the IdP issues a signed JSON Web Token (JWT), called an ID token, that carries the user’s identity and attributes in a verifiable, tamper-evident form.</p>
<p>Using that model for MySQL authentication brings several practical advantages over password-based accounts:</p>
<ul>
<li><strong>Alignment with single sign-on.</strong> Users authenticate once with their IdP and can reuse that session context across OIDC-aware applications, including databases. User lifecycle and password management remain centralized.</li>
<li><strong>No long-lived secrets on the wire.</strong> ID tokens are short-lived and cryptographically signed, so there is no static password to steal, rotate, or accidentally commit to a configuration file.</li>
<li><strong>Support for hybrid deployments.</strong> Organizations that run MySQL on-premises while hosting applications in the cloud can still authenticate through the same identity plane on both sides.</li>
<li><strong>Broad interoperability.</strong> Because OpenID Connect is a widely adopted standard, the plugin can work with any compliant provider, including Keycloak, Okta, Microsoft Entra ID, and Google Identity.</li>
</ul>
<p>None of that is unique to Percona; Oracle makes a similar value proposition for the Enterprise plugin. The real difference lies in how much operational burden the plugin removes from the administrator, which becomes clear in the next sections.</p>
<h2>How OpenID Connect Authentication Works</h2>
<p><img decoding="async" class="alignnone size-medium wp-image-52740" src="https://www.percona.com/wp-content/uploads/2026/09/percona_oidc_flow-300x146.png" alt="" width="100%" srcset="https://www.percona.com/wp-content/uploads/2026/09/percona_oidc_flow-300x146.png 300w, https://www.percona.com/wp-content/uploads/2026/09/percona_oidc_flow-1024x498.png 1024w, https://www.percona.com/wp-content/uploads/2026/09/percona_oidc_flow-768x373.png 768w, https://www.percona.com/wp-content/uploads/2026/09/percona_oidc_flow-1536x747.png 1536w, https://www.percona.com/wp-content/uploads/2026/09/percona_oidc_flow.png 1629w" sizes="(max-width: 300px) 100vw, 300px"></p>
<p>Once the plugin and its configuration are in place, the authentication path is the same regardless of which IdP issued the token:</p>
<ol>
<li><strong>The user authenticates to the IdP</strong> and receives a signed ID token.</li>
<li><strong>The token is written to a local file</strong> that only the client operating system account can read.</li>
<li><strong>The MySQL client</strong> uses an option that causes the client-side OIDC plugin to load and read the token from the file. <strong>The token is sent to the server</strong> as part of the authentication handshake.</li>
<li><strong>The server validates the secure channel</strong> and decodes the token. It then <strong>verifies the token signature</strong> using the selected IdP’s public key, <strong>checks the expiration time</strong>, and <strong>validates the configured claims</strong>.</li>
<li><strong>The server resolves the final identity</strong> as either a personal account or a group-based proxy target. The plugin may also return roles mapped from the user’s group membership.</li>
</ol>
<h2>Configuring Trusted Providers and Letting the Plugin Manage the Keys</h2>
<p>Identity Providers rotate their signing keys periodically as a basic security measure. If a key is ever compromised, limiting its lifetime reduces the potential impact, and regular rotation also lowers the long-term value of any one key as a target. In practice, rotation is gradual: a new key is published and accepted before it starts signing tokens, and an old key remains valid for a period after it stops signing so that tokens already in flight can still be verified.</p>
<p>Public keys are exposed through the standard JWKS (JSON Web Key Set) endpoint, which applications can use to verify tokens issued by the IdP <a href="https://www.percona.com/blog/oidc-authentication-for-percona-mysql/#jwks">[6]</a>.</p>
<p>The Percona OpenID Connect authentication plugin can download public keys from a configured JWKS endpoint when the plugin is loaded, typically during installation and server startup, and store them in a cache. It also provides a User Defined Function (UDF) that can refresh the cache on demand or periodically through the Event Scheduler.</p>
<p>By contrast, Oracle’s counterpart plugin requires signing keys to be configured statically through the <strong>authentication_openid_connect_configuration</strong> server variable, supplied either as an inline JSON string or as a path to a JSON file. There is no retrieval or refresh from the JWKS endpoint, so keeping keys current after each rotation remains a manual task for the administrator. In the window just after a rotation, tokens signed with the previous key are still valid but cannot be verified until the configuration is updated. Percona’s plugin supports static key configuration as well, but that mode is better suited to testing or temporary setups than to production.</p>
<h3>Example</h3>
<p>Using the feature requires two simple steps. First, JWKS endpoint URL must be set in the plugin’s configuration. For example, the below configuration defines IdP named as example-keycloak (pay attention to jwks-url element):</p><pre class="urvanov-syntax-highlighter-plain-tag">{
  "example-keycloak": {
  "issuer-name": "https://keycloak.example.com/realms/master",
  "jwks-url": "https://keycloak.example.com/realms/master/protocol/openid-connect/certs",
  "audiences": [ "mysql-oidc" ]
  }
}</pre><p>The second step is ensuring the MySQL event scheduler is running and creating an event updating the keys. For example, to enable updating the keys for example-keycloak every hour run from MySQL client:</p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE EVENT update_oidc_keys
  ON SCHEDULE EVERY 1 HOUR
  DO SELECT update_jwks("example-keycloak");</pre><p></p>
<h2>Benefits of Using IdP Groups</h2>
<p>This is where Percona’s plugin diverges most clearly from the Enterprise implementation.</p>
<p>Groups are managed by the corporate Identity Provider and group membership may be carried by ID tokens. OIDC does not define a standard claim for that, but most IdP implementations allow adding a group claim to the tokens. The Percona’s plugin allows the administrator to configure the group claim name so that it matches the token format used by the chosen IdP.</p>
<p>There are two practical ways to take advantage of this feature:  group-to-role mapping and proxy users.</p>
<h2>Group-to-Role Mapping</h2>
<p>Membership in a group can automatically translate into MySQL roles and therefore privileges across multiple MySQL servers at the same time. On a single server, the flow looks like this:</p>
<ol>
<li>The administrator <strong>creates roles and grants them privileges</strong>.</li>
<li>The administrator defines the <strong>IdP group-to-MySQL role mapping</strong> in the plugin configuration file.</li>
<li>When the user connects, the plugin returns the roles that match the user’s groups, and the server <strong>automatically grants those roles to the user</strong>.</li>
<li>The user can activate any granted role and <strong>exercise the privileges assigned to it</strong>.</li>
</ol>
<p>Please note, that group-to-role mapping still requires an account created for each user, but automates managing user privileges.</p>
<h3>Example</h3>
<p>To create roles and grant them some privileges one may run:</p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE ROLE accounting;
GRANT ALL PRIVILEGES ON accounting_database.* TO accounting;
CREATE ROLE sales;
GRANT ALL PRIVILEGES ON sales_database.* TO sales;</pre><p>Then, to to define the mapping add to IDP configuration:</p><pre class="urvanov-syntax-highlighter-plain-tag">"group-claim": "groups",
"group-role": [
  { "/accounting": "accounting" },
  { "/marketing": "marketing" }
]</pre><p>Any user connecting with an ID token containing claim <strong>“groups”:[“/accounting”]</strong> will be granted with role accounting and effectively obtain access to <strong>accounting_database</strong> and so on.</p>
<h2>Proxy Users</h2>
<p>The proxy capability in MySQL allows an authentication plugin to request that the connecting external user be logged in as a different MySQL user. In this model, the external identity is the <em>proxy user</em> and the mapped MySQL account is the <em>proxied user</em>. The purpose is to <strong>let multiple users share accounts with the same privilege set, avoiding the need to create a separate personal database account for every individual</strong>.</p>
<p>This feature must be supported by the authentication plugin, whose job is to choose the proxied user according to the specifics of the authentication method. In the Percona OIDC plugin, that selection is based on the group claim in the token and works as follows:</p>
<ol>
<li>The administrator <strong>creates a proxy user</strong> identified by the OIDC plugin. This can be either a single anonymous account (”@”) without a specific group name, referred to as anonymous proxying, or multiple group-related accounts, referred to as named group proxying.</li>
<li>The administrator <strong>creates proxied users for each group</strong>. These accounts should not use a login plugin, so nobody can connect to them directly. The username must match the group name.</li>
<li>The administrator <strong>grants the PROXY privilege for each proxy user</strong> on all related proxied users.</li>
<li>When a user connects, in the <strong>anonymous proxying</strong> case the plugin returns the user’s <strong>first group as the proxied username</strong>. In the <strong>named group proxying</strong> case, the plugin <strong>checks whether the user belongs to the group and returns that group</strong> as the proxied username.</li>
<li>The server verifies that the requested proxied account exists and that the proxy user has the required PROXY privilege on it. If both checks succeed, <strong>the session runs with the proxied account’s privileges</strong>.</li>
</ol>
<h2>Other Features</h2>
<p>Supported signing algorithms include RSASSA-PKCS1-v1_5, RSASSA-PSS, and ECDSA with SHA-256, SHA-384, and SHA-512 hashing functions.</p>
<p>The Percona approach uses the client-side OpenID Connect plugin from upstream MySQL, which ensures compatibility with the standard Oracle client.</p>
<p>Both client-side and server-side OpenID Connect plugins ensure that the token is sent via a secure channel. Accepted protocols are TCP protected by TLS, Unix sockets, and shared memory.</p>
<h2>What OpenID Connect Authentication Does Not Do</h2>
<p>There are some limits worth knowing.</p>
<p>The first comes from MySQL’s authentication design: any authentication plugin is used at connection time only. In the case of OIDC, the token is validated when the user connects, and a session that stays open may outlive the ID token that opened it. There is no out-of-the-box mechanism to force re-authentication after some time (except for idle connection timeout).</p>
<p>A similar situation applies to group-role mapping. The roles tied to the user’s groups in the ID token are granted or revoked at connection time. As a result, if a user is added to or removed from an IdP group, they must reconnect to Percona Server for the change to be reflected in their granted roles.</p>
<p>The proxying mechanism uses group membership claim instead of the token’s subject, so any token signed by a configured IdP that carries the required group is accepted. Group membership is your trust boundary in those modes, so treat it that way.</p>
<p>The current proxying implementation assumes the proxied user’s name matches the group name. This can be a problem when a group name isn’t a valid MySQL username (for example, it’s too long or contains disallowed characters), or when multiple groups need to map to a single account. We plan to add group-to-proxied-account mapping in future releases to address this.</p>
<p>The client-side plugin doesn’t verify the ID token (for example, check whether it has expired) before connecting, and the server doesn’t report the reason for access being denied (for security reasons). A good practice is to obtain a fresh token before connecting.</p>
<h2>Conclusion</h2>
<p>Functionally, Percona’s OpenID Connect plugin covers the same core ground as the counterpart in MySQL Enterprise Edition: signed ID tokens, claim validation, subject matching, and secure-transport enforcement.</p>
<p>It goes further in several important areas:</p>
<ul>
<li>It is open source.</li>
<li>Keys can stay current automatically through JWKS synchronization.</li>
<li>Group-to-role mapping allows IdP group membership to drive MySQL role grants for the lifetime of the session.</li>
<li>Proxy-user support allows many IdP identities to share a smaller set of MySQL accounts.</li>
</ul>
<p>Our OIDC implementation is suitable for real-world identity operations at scale. It can automatically map identities and groups managed by an IdP to database users and roles, and synchronize cryptographic keys.</p>
<h2>References</h2>
<ol>
<li><a href="https://docs.percona.com/percona-server/8.4/openid-connect-authentication.html">Percona Server for MySQL documentation: OpenID Connect authentication</a>.</li>
<li><a href="https://docs.percona.com/percona-server/8.4/quickstart-openid-connect.html">Percona Server for MySQL documentation: Get started with OpenID Connect authentication</a>.</li>
<li><a href="https://dev.mysql.com/doc/refman/9.7/en/openid-pluggable-authentication.html">MySQL 9.7 Reference Manual: OpenID Connect Pluggable Authentication</a>.</li>
<li><a href="https://dev.mysql.com/doc/refman/9.7/en/proxy-users.html">MySQL 9.7 Reference Manual: Proxy Users</a>.</li>
<li><a href="https://openid.net/foundation/how-connect-works/">OpenID Foundation: How OpenID Connect Works</a></li>
<li><a href="https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets">auth0 Docs: JSON Web Key Sets</a>.</li>
</ol>
<hr>
<p><em>Written by Michal Jankowski. Reviewed by Dennis Kittrell and Oleksiy Lukin.<br>
Percona® is a registered trademark of Percona LLC. MySQL® is a registered trademark of Oracle Corporation.<br>
</em></p>
<p>The post <a href="https://www.percona.com/blog/oidc-authentication-for-percona-mysql/">OpenID Connect Authentication for MySQL, Now Fully Open Source</a> appeared first on <a href="https://www.percona.com/">Percona</a>.</p>]]></content:encoded>
    <pubDate>Wed, 02 Sep 2026 08:56:26 +0000</pubDate>
    <dc:creator>MySQL Performance Blog</dc:creator>
    <category>MySQL</category>
    <category>Percona Software</category>
    <category>Security</category>
    <category>authentication</category>
    <category>JWKS</category>
    <category>OIDC</category>
    <category>OpenID Connect</category>
    <category>Percona</category>
  </item>

  <item>
    <title>MariaDB/MySQL Environment MyEnv 3.0.1 has been released</title>
    <guid isPermaLink="false">https://www.fromdual.com/blog/myenv-release-notes/fromdual-environment-myenv-3.0.1-has-been-released/</guid>
    <link>https://www.fromdual.com/blog/myenv-release-notes/fromdual-environment-myenv-3.0.1-has-been-released/</link>
    <description>FromDual has the pleasure to announce the release of the new version 3.0.1 of its popular MariaDB, MySQL and PostgreSQL multi-instance environment MyEnv.
The new MyEnv can be downloaded here. How to install MyEnv is described in the MyEnv Installation Guide.
In the inconceivable case that you find a bug in the MyEnv please report it to our public repository on Codeberg.
Any feedback, statements and testimonials are welcome as well! Please send them to us.
Upgrade from 2.x to 3.0
Please check the MyEnv Installation Guide: Upgrading MyEnv.
In MyEnv v3.0.1 tpl/aliases.conf.template has changed. Thus you should replace the MyEnv aliases.conf as follows:
$ cp /etc/myenv/aliases.conf /etc/myenv/aliases.conf.$(date '+%Y-%m-%d')
$ cp myenv/tpl/aliases.conf.template /etc/myenv/aliases.conf
Changes in MyEnv 3.0.1
MyEnv

Bug in setMyEnv.php fixed, socket was not set correctly.
Some more aliases added for log tracking.
Prompt port was always delayed by one switch, fixed.
Missing port in PS1 prompt fixed again (issues/12).
up/down for PostgreSQL should work correctly now.
PostgreSQL should be evaluated correctly now in up.
up should show PostgreSQL status correctly now (issues/4).
Environment variables refactored and PostgreSQL specific variables added (issues/3).
Discrepancies between my.cnf and myenv.conf leads now to abort if relevant configuration variables are affected (issues/5).
Database seems OK after start which is wrong, timing issue (issues/2).
hideschema works better now (issues/13).

MyEnv Installer

Wrong default for socket fixed in installer.
Nasty error message when installing PostgreSQL suppressed.
basedir filter implemented.
Instance type mariadb was added to MyEnv installer (issues/10).
MySQL 26.7 is recognized correctly now (issues/1).

MyEnv Utilities

insert_test.sh made more PostgreSQL friendly.
test table commands made more comfortable for PostgreSQL.

PostgreSQL

See MyEnv, MyEnv Installer and MyEnv Utilities.

General

Cosmetic fixes.
Some bugs and feature requests moved from TODO to Codeberg.
Error message added to make more clear what is wrong.
rc made unique.
Error messages improved to see base problem better.

Documentation

“tar ball” vs “tarball” typo fix.
Documentation updated.
Upgrade recommendations added.
CHANGELOG updated.
README unified and updated.

Packaging

Some minor build fixes.
Ubuntu 26.04 container added to build infrastructure.

For subscriptions of commercial use of MyEnv please get in contact with us.</description>
    <content:encoded><![CDATA[<p>FromDual has the pleasure to announce the release of the new version 3.0.1 of its popular MariaDB, MySQL and PostgreSQL multi-instance environment <a href="https://www.fromdual.com/software/fromdual-myenv/" title="MariaDB, MySQL and PostgreSQL multi-instance environment">MyEnv</a>.</p>
<p>The new MyEnv can be downloaded <a href="https://support.fromdual.com/admin/public/download.php?operation=select&amp;product_id=5" target="_blank" title="FromDual download">here</a>. How to install MyEnv is described in the <a href="https://support.fromdual.com/documentation/myenv/myenv.html#installation-guide" target="_blank">MyEnv Installation Guide</a>.</p>
<p>In the inconceivable case that you find a bug in the MyEnv please report it to our public repository on <a href="https://codeberg.org/FromDual/MyEnv/issues" target="_blank" title="FromDual / MyEnv">Codeberg</a>.</p>
<p>Any feedback, statements and testimonials are welcome as well! Please <a href="mailto:feedback@fromdual.com?Subject=Feedback%20for%20myenv">send them to us</a>.</p>
<h2>Upgrade from 2.x to 3.0</h2>
<p>Please check the MyEnv Installation Guide: <a href="https://support.fromdual.com/documentation/myenv/myenv.html#upgrade" target="_blank">Upgrading MyEnv</a>.</p>
<p>In MyEnv v3.0.1 <code>tpl/aliases.conf.template</code> has changed. Thus you should replace the MyEnv <code>aliases.conf</code> as follows:</p>
<pre tabindex="0"><code>$ cp /etc/myenv/aliases.conf /etc/myenv/aliases.conf.$(date '+%Y-%m-%d')
$ cp myenv/tpl/aliases.conf.template /etc/myenv/aliases.conf
</code></pre><h2>Changes in MyEnv 3.0.1</h2>
<h3>MyEnv</h3>
<ul>
<li>Bug in <code>setMyEnv.php</code> fixed, socket was not set correctly.</li>
<li>Some more aliases added for log tracking.</li>
<li>Prompt port was always delayed by one switch, fixed.</li>
<li>Missing port in <code>PS1</code> prompt fixed again (<a href="https://codeberg.org/FromDual/MyEnv/issues/12" target="_blank">issues/12</a>).</li>
<li><code>up</code>/<code>down</code> for PostgreSQL should work correctly now.</li>
<li>PostgreSQL should be evaluated correctly now in <code>up</code>.</li>
<li><code>up</code> should show PostgreSQL status correctly now (<a href="https://codeberg.org/FromDual/MyEnv/issues/4" target="_blank">issues/4</a>).</li>
<li>Environment variables refactored and PostgreSQL specific variables added (<a href="https://codeberg.org/FromDual/MyEnv/issues/3" target="_blank">issues/3</a>).</li>
<li>Discrepancies between <code>my.cnf</code> and <code>myenv.conf</code> leads now to abort if relevant configuration variables are affected (<a href="https://codeberg.org/FromDual/MyEnv/issues/5" target="_blank">issues/5</a>).</li>
<li>Database seems OK after start which is wrong, timing issue (<a href="https://codeberg.org/FromDual/MyEnv/issues/2" target="_blank">issues/2</a>).</li>
<li><code>hideschema</code> works better now (<a href="https://codeberg.org/FromDual/MyEnv/issues/13" target="_blank">issues/13</a>).</li>
</ul>
<h3>MyEnv Installer</h3>
<ul>
<li>Wrong default for socket fixed in installer.</li>
<li>Nasty error message when installing PostgreSQL suppressed.</li>
<li><code>basedir</code> filter implemented.</li>
<li>Instance <code>type</code> <code>mariadb</code> was added to MyEnv installer (<a href="https://codeberg.org/FromDual/MyEnv/issues/10" target="_blank">issues/10</a>).</li>
<li>MySQL 26.7 is recognized correctly now (<a href="https://codeberg.org/FromDual/MyEnv/issues/1" target="_blank">issues/1</a>).</li>
</ul>
<h3>MyEnv Utilities</h3>
<ul>
<li><code>insert_test.sh</code> made more PostgreSQL friendly.</li>
<li><code>test</code> table commands made more comfortable for PostgreSQL.</li>
</ul>
<h3>PostgreSQL</h3>
<ul>
<li>See <a href="https://www.fromdual.com/blog/myenv-release-notes/fromdual-environment-myenv-3.0.1-has-been-released/#myenv">MyEnv</a>, <a href="https://www.fromdual.com/blog/myenv-release-notes/fromdual-environment-myenv-3.0.1-has-been-released/#myenv-installer">MyEnv Installer</a> and <a href="https://www.fromdual.com/blog/myenv-release-notes/fromdual-environment-myenv-3.0.1-has-been-released/#myenv-utilities">MyEnv Utilities</a>.</li>
</ul>
<h3>General</h3>
<ul>
<li>Cosmetic fixes.</li>
<li>Some bugs and feature requests moved from <code>TODO</code> to Codeberg.</li>
<li>Error message added to make more clear what is wrong.</li>
<li>rc made unique.</li>
<li>Error messages improved to see base problem better.</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>“tar ball” vs “tarball” typo fix.</li>
<li>Documentation updated.</li>
<li>Upgrade recommendations added.</li>
<li><code>CHANGELOG</code> updated.</li>
<li><code>README</code> unified and updated.</li>
</ul>
<h3>Packaging</h3>
<ul>
<li>Some minor build fixes.</li>
<li>Ubuntu 26.04 container added to build infrastructure.</li>
</ul>
<p>For subscriptions of commercial use of MyEnv please <a href="mailto:contact@fromdual.com?Subject=Commercial%20use%20of%20MyEnv">get in contact</a> with us.</p>]]></content:encoded>
    <pubDate>Wed, 02 Sep 2026 07:51:00 +0000</pubDate>
    <dc:creator>FromDual</dc:creator>
  </item>

  <item>
    <title>100 SQL MCQ with Answers (SQL Test 2026)</title>
    <guid isPermaLink="false">https://codeforgeek.com/?p=36613</guid>
    <link>https://codeforgeek.com/100-sql-mcq-with-answers/</link>
    <description>These 100 SQL MCQs with answers cover the concepts that appear repeatedly in beginner assessments, from SELECT and filtering through joins, grouping, constraints, and set operations. Answer each question before opening its explanation, then use every miss to name the SQL concept you need to practise in a database. How to use these SQL MCQ […]</description>
    <pubDate>Tue, 01 Sep 2026 16:26:39 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Mysql</category>
  </item>

  <item>
    <title>Java and MySQL Connectivity Using JDBC</title>
    <guid isPermaLink="false">http://codeforgeek.com/?p=145</guid>
    <link>https://codeforgeek.com/java-mysql-connectivity-jdbc/</link>
    <description>Java Database Connectivity (JDBC) gives a Java program a standard API for opening a MySQL connection, sending SQL, and reading rows. The work starts with a compatible MySQL driver, a complete JDBC URL, and an account that can reach only the database your program needs. I compiled the example below with Maven resolving MySQL Connector/J […]</description>
    <pubDate>Tue, 01 Sep 2026 08:19:13 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Database</category>
    <category>Java</category>
    <category>Mysql</category>
    <category>Tutorials</category>
  </item>

  <item>
    <title>Debugging MySQL Memory Alerts: When innodb_buffer_pool_size Isn't the Whole Story</title>
    <guid isPermaLink="false">tag:blogger.com,1999:blog-2121546342550511109.post-4029181527135665125</guid>
    <link>https://mysql-ninjas.blogspot.com/2026/08/debugging-mysql-memory-alerts-when.html</link>
    <description>

  Posted on MySQL Ninjas | August 2026Every DBA has been there. An alert fires. You SSH into the box, run free -h, and see MySQL consuming far more RAM than you configured. You double-check innodb_buffer_pool_size. It's set correctly. So where is the memory going?

  This is the story of debugging exactly that — on a Google Cloud c4d-standard-4 instance with 14.7 GB RAM, MySQL configured with a 7168 MB buffer pool, and mysqld RSS sitting at 10.4 GB. That's a 3.2 GB gap nobody could explain.

  

  The Setup

  We run a large MySQL fleet on GCP — over 200 DR replica nodes across three datacenters. As part of a buffer pool tuning project (fitting the right pool size to the right machine class), we set innodb_buffer_pool_size = 7168 MB on our c4d-standard-4 nodes (16 GB RAM, 14.7 GB usable).

  Shortly after, memory alerts started firing. When I looked at what was actually happening:

  $ ps aux | grep mysqld | awk '{print $6/1024 &quot; MB&quot;}'
10490 MB

  mysqld RSS: ~10,490 MB.
  Configured buffer pool: 7,168 MB.
  Unexplained overhead: ~3,300 MB.

  That's not rounding error. Something was eating memory we hadn't accounted for.

  

  Layer 1: The Expected Overhead

  The first thing to understand is that MySQL's RSS is never equal to innodb_buffer_pool_size. There are well-known, legitimate sources of overhead:

  
    
      
        Component
        Approximate Size
      
    
    
      
        InnoDB internal structures (AHI, change buffer, log buffer)
        200–400 MB
      
      
        Performance Schema
        100–300 MB (depends on config)
      
      
        Per-thread buffers (sort, join, read)
        Varies
      
      
        Connection overhead
        ~1 MB × max_connections
      
    
  

  On our node, max_connections = 3000. That's potentially 3 GB of THD memory root alone — a massive risk. We confirmed via sys.memory_by_thread_by_current_bytes that THD::main_mem_root had peaked at 7.7 GB under load. This alone was an OOM risk, not just a memory mystery.

  Fix #1: Reduce max_connections from 3000 → 500 for DR replicas. They don't serve application traffic; they don't need 3000 slots.

  After accounting for all of this, we could explain roughly 1,500–1,800 MB of the gap. We still had ~1,500 MB unaccounted for.

  

  Layer 2: InnoDB Buffer Pool Chunk Overhead (~9% mmap Padding)

  InnoDB allocates the buffer pool in chunks (innodb_buffer_pool_chunk_size, default 128 MB). When MySQL calls mmap() to allocate these chunks, the kernel doesn't give you exactly what you asked for — there's alignment overhead, guard pages, and internal bookkeeping in the virtual memory subsystem.

  In practice, InnoDB's actual RSS from the buffer pool is roughly 9% higher than the configured size:

  7168 MB × 1.09 ≈ 7813 MB

  This is documented nowhere prominently, but you can verify it empirically by inspecting /proc/&amp;lt;pid&amp;gt;/smaps and looking for the large anonymous mappings:

  $ grep -A2 &quot;7[0-9][0-9][0-9][0-9] kB&quot; /proc/$(pgrep mysqld)/smaps | head -40

  You'll see several large anon regions just over 128 MB each — these are your buffer pool chunks, with mmap overhead baked in.

  So after accounting for chunk overhead: 7813 MB + ~1500 MB other = ~9313 MB. We were at 10,490 MB. Still ~1,177 MB unexplained.

  

  Layer 3: jemalloc — The Hidden Memory Manager

  Here's where it gets interesting.

  Our MySQL is started with:

  LD_PRELOAD=/usr/lib64/libjemalloc.so.2

  jemalloc is a high-performance memory allocator that replaces glibc's malloc. It's widely used in MySQL deployments to reduce fragmentation. But jemalloc has a behavior that surprises people: it holds onto freed memory as &quot;dirty&quot; pages, ready to hand back to threads quickly without going back to the OS.

  Check if you're running jemalloc:

  $ strings /proc/$(pgrep mysqld)/environ | grep LD_PRELOAD

  On a busy primary, dirty pages are constantly churned and released. On an idle replica — like a DR slave not actively serving reads — jemalloc dirty pages accumulate and are never returned to the OS because there's no memory pressure to trigger eviction.

  We confirmed this by inspecting /proc/&amp;lt;pid&amp;gt;/smaps for dirty anonymous pages not accounted for by InnoDB mappings. The amount held by jemalloc dirty pages on our idle DR replica: approximately 125 MB.

  

  Layer 4: The gdb Arena Purge (Emergency Release)

  To prove the jemalloc dirty-page theory and release the memory without restarting MySQL, we used a technique that feels like surgery: calling mallctl via gdb while MySQL was live.

  First, find how many arenas exist:

  $ gdb -p $(pgrep mysqld) --batch \
  -ex 'call (int)mallctl(&quot;opt.narenas&quot;, 0, 0, 0, 0)'

  Then purge all arenas:

  $ gdb -p $(pgrep mysqld) --batch \
  -ex 'call mallctl(&quot;arena.0.purge&quot;, 0, 0, 0, 0)' \
  -ex 'call mallctl(&quot;arena.1.purge&quot;, 0, 0, 0, 0)' \
  -ex 'call mallctl(&quot;arena.2.purge&quot;, 0, 0, 0, 0)' \
  -ex 'detach'

  After purging all arenas, RSS dropped by approximately 125 MB — confirming jemalloc dirty pages were the source. But it also confirmed they were only ~125 MB, not the ~1,500 MB we initially suspected.

  
    Warning: Running gdb against a live MySQL instance briefly pauses the process. Use this only on non-critical instances (DR replicas, test nodes). Never on a primary under active traffic.
  

  

  The Root Cause: MALLOC_CONF Not Set

  The deeper issue wasn't the dirty pages themselves — it was that jemalloc's background decay was completely disabled.

  jemalloc 5.x has a background_thread feature: a dedicated thread that periodically purges dirty pages back to the OS on a decay schedule. Without it, dirty pages only get released when a new allocation request needs the memory.

  Check your jemalloc config:

  $ strings /proc/$(pgrep mysqld)/environ | grep MALLOC_CONF
# if nothing returns, MALLOC_CONF is not set

  On our node: MALLOC_CONF was not set at all. That means:

  
    background_thread = false (default)
    dirty_decay_ms = 10000 ms — but only triggered by allocation activity
    muzzy_decay_ms = 10000 ms (same)
  

  On an idle DR replica, there's minimal allocation activity. Decay never triggers. Dirty pages accumulate indefinitely.

  

  The Fixes

  Fix 1: Tune innodb_buffer_pool_size down slightly

  Instead of 7168 MB, use 6144 MB on c4d-standard-4 nodes. This is a clean multiple of the 128 MB chunk size (48 chunks), saves ~1.1 GB RSS vs. 7168 MB accounting for mmap overhead, and leaves more headroom for thread buffers.

  # Puppet/Hiera
percona::server::config::innodb_pool_size: 6144

  Fix 2: Reduce max_connections

  DR replicas don't serve application reads. 3,000 connections is both wasteful and an OOM risk.

  max_connections = 500

  Fix 3: Enable jemalloc background_thread with decay

  Add to /etc/sysconfig/mysql (requires mysqld restart):

  MALLOC_CONF=&quot;background_thread:true,dirty_decay_ms:5000,muzzy_decay_ms:5000&quot;

  This tells jemalloc to run a dedicated background thread to decay dirty pages within 5 seconds, even on an idle process. After this change, jemalloc RSS overhead on idle replicas dropped to near zero.

  Fix 4: Add Swap

  Our DR nodes had zero swap. When the OOM killer fires with no swap, it's immediate and brutal — no warning, no time to respond. Even 4 GB buys you time to react.

  fallocate -l 4G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' &amp;gt;&amp;gt; /etc/fstab

  

  The Full Memory Accounting (Before vs. After)

  
    
      
        Component
        Before
        After
      
    
    
      
        InnoDB buffer pool (configured)
        7,168 MB
        6,144 MB
      
      
        InnoDB mmap chunk overhead (~9%)
        ~645 MB
        ~553 MB
      
      
        InnoDB internals (AHI, log buffer, etc.)
        ~300 MB
        ~300 MB
      
      
        Performance Schema
        ~150 MB
        ~150 MB
      
      
        Per-thread buffers
        ~2,000 MB (3000 conns)
        ~500 MB (500 conns)
      
      
        jemalloc dirty pages
        ~125 MB (accumulating)
        ~50 MB (background decay)
      
      
        OS + binary + misc
        ~200 MB
        ~200 MB
      
      
        Total mysqld RSS
        ~10,490 MB
        ~7,897 MB
      
    
  

  

  Key Takeaways

  
    mysqld RSS ≈ buffer_pool × 1.09 + everything else. The 9% InnoDB mmap overhead is real and underdocumented.
    max_connections is memory, not just a connection limit. 3,000 connections × ~1 MB THD overhead = 3 GB potential. Size it for actual use.
    jemalloc on idle replicas accumulates dirty pages. If MALLOC_CONF isn't set, background_thread is off and decay only happens during allocation pressure. Fix it with background_thread:true.
    gdb arena purge is a valid diagnostic tool on non-critical instances — but it's a band-aid. Fix MALLOC_CONF properly.
    No swap = no warning before OOM. Even 4 GB buys you response time before the OOM killer fires.
    Check /proc/&amp;lt;pid&amp;gt;/smaps when RSS is mysterious. It shows every memory region with sizes and dirty-page counts.
  

  

  Quick Diagnostic Checklist

  # 1. Current mysqld RSS
ps -o rss= -p $(pgrep mysqld) | awk '{print $1/1024 &quot; MB&quot;}'

# 2. Buffer pool size
mysql -e &quot;SHOW VARIABLES LIKE 'innodb_buffer_pool_size'&quot;

# 3. Are you running jemalloc?
strings /proc/$(pgrep mysqld)/environ | grep LD_PRELOAD

# 4. Is MALLOC_CONF set?
strings /proc/$(pgrep mysqld)/environ | grep MALLOC_CONF

# 5. Max connections configured vs. peak usage
mysql -e &quot;SHOW VARIABLES LIKE 'max_connections'; SHOW STATUS LIKE 'Max_used_connections'&quot;

# 6. Swap availability
free -h

# 7. smaps summary (requires root)
awk '/^Rss:/{r+=$2} /^Dirty:/{d+=$2} END{print &quot;RSS:&quot;, r/1024, &quot;MB | Dirty:&quot;, d/1024, &quot;MB&quot;}' \
  /proc/$(pgrep mysqld)/smaps

  

  If this helped you track down a memory mystery, drop a comment below. MySQL memory accounting is genuinely complex — the more we share real debugging stories, the better the community gets at it.

</description>
    <content:encoded><![CDATA[<div>

  <p>Posted on MySQL Ninjas | August 2026</p><p>Every DBA has been there. An alert fires. You SSH into the box, run <code>free -h</code>, and see MySQL consuming far more RAM than you configured. You double-check <code>innodb_buffer_pool_size</code>. It's set correctly. So where is the memory going?</p>

  <p>This is the story of debugging exactly that — on a Google Cloud <code>c4d-standard-4</code> instance with 14.7 GB RAM, MySQL configured with a 7168 MB buffer pool, and mysqld RSS sitting at <strong>10.4 GB</strong>. That's a 3.2 GB gap nobody could explain.</p><div class="separator"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2oontYOJZDNQhQnAq2QkZ_kWVkHNFZ4SgdDTkCHZyUreUAT0IMH_UYrun_NVgLCMceg0b3VxZMFIOd9Ctnu9eKNrZIcX2SzdwZX_pArWSZ5NZ0YGuunJB1_6OLbjf_voKq2PjeTnvMoyqpQPrN6uz-QSoPsRJZ2ECretQfIUPfNoT6KVsfsVmZBt3WelX/s1536/ChatGPT%20Image%20Aug%2031,%202026,%2004_10_55%20PM.png" imageanchor="1"><img border="0" data-original-height="1024" data-original-width="1536" height="266" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2oontYOJZDNQhQnAq2QkZ_kWVkHNFZ4SgdDTkCHZyUreUAT0IMH_UYrun_NVgLCMceg0b3VxZMFIOd9Ctnu9eKNrZIcX2SzdwZX_pArWSZ5NZ0YGuunJB1_6OLbjf_voKq2PjeTnvMoyqpQPrN6uz-QSoPsRJZ2ECretQfIUPfNoT6KVsfsVmZBt3WelX/w400-h266/ChatGPT%20Image%20Aug%2031,%202026,%2004_10_55%20PM.png" width="400"></a></div>

  <hr>

  <h2>The Setup</h2>

  <p>We run a large MySQL fleet on GCP — over 200 DR replica nodes across three datacenters. As part of a buffer pool tuning project (fitting the right pool size to the right machine class), we set <code>innodb_buffer_pool_size = 7168 MB</code> on our <code>c4d-standard-4</code> nodes (16 GB RAM, 14.7 GB usable).</p>

  <p>Shortly after, memory alerts started firing. When I looked at what was actually happening:</p>

  <pre>$ ps aux | grep mysqld | awk '{print $6/1024 " MB"}'
10490 MB</pre>

  <p>mysqld RSS: <strong>~10,490 MB</strong>.<br>
  Configured buffer pool: <strong>7,168 MB</strong>.<br>
  Unexplained overhead: <strong>~3,300 MB</strong>.</p>

  <p>That's not rounding error. Something was eating memory we hadn't accounted for.</p>

  <hr>

  <h2>Layer 1: The Expected Overhead</h2>

  <p>The first thing to understand is that MySQL's RSS is <em>never</em> equal to <code>innodb_buffer_pool_size</code>. There are well-known, legitimate sources of overhead:</p>

  <table>
    <thead>
      <tr>
        <th>Component</th>
        <th>Approximate Size</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>InnoDB internal structures (AHI, change buffer, log buffer)</td>
        <td>200–400 MB</td>
      </tr>
      <tr>
        <td>Performance Schema</td>
        <td>100–300 MB (depends on config)</td>
      </tr>
      <tr>
        <td>Per-thread buffers (sort, join, read)</td>
        <td>Varies</td>
      </tr>
      <tr>
        <td>Connection overhead</td>
        <td>~1 MB × max_connections</td>
      </tr>
    </tbody>
  </table>

  <p>On our node, <code>max_connections = 3000</code>. That's potentially <strong>3 GB</strong> of THD memory root alone — a massive risk. We confirmed via <code>sys.memory_by_thread_by_current_bytes</code> that <code>THD::main_mem_root</code> had peaked at <strong>7.7 GB</strong> under load. This alone was an OOM risk, not just a memory mystery.</p>

  <p><strong>Fix #1:</strong> Reduce <code>max_connections</code> from 3000 → 500 for DR replicas. They don't serve application traffic; they don't need 3000 slots.</p>

  <p>After accounting for all of this, we could explain roughly 1,500–1,800 MB of the gap. We still had ~1,500 MB unaccounted for.</p>

  <hr>

  <h2>Layer 2: InnoDB Buffer Pool Chunk Overhead (~9% mmap Padding)</h2>

  <p>InnoDB allocates the buffer pool in <strong>chunks</strong> (<code>innodb_buffer_pool_chunk_size</code>, default 128 MB). When MySQL calls <code>mmap()</code> to allocate these chunks, the kernel doesn't give you exactly what you asked for — there's alignment overhead, guard pages, and internal bookkeeping in the virtual memory subsystem.</p>

  <p>In practice, InnoDB's actual RSS from the buffer pool is roughly <strong>9% higher</strong> than the configured size:</p>

  <pre>7168 MB × 1.09 ≈ 7813 MB</pre>

  <p>This is documented nowhere prominently, but you can verify it empirically by inspecting <code>/proc/&lt;pid&gt;/smaps</code> and looking for the large anonymous mappings:</p>

  <pre>$ grep -A2 "7[0-9][0-9][0-9][0-9] kB" /proc/$(pgrep mysqld)/smaps | head -40</pre>

  <p>You'll see several large <code>anon</code> regions just over 128 MB each — these are your buffer pool chunks, with mmap overhead baked in.</p>

  <p>So after accounting for chunk overhead: <code>7813 MB + ~1500 MB other = ~9313 MB</code>. We were at 10,490 MB. Still ~1,177 MB unexplained.</p>

  <hr>

  <h2>Layer 3: jemalloc — The Hidden Memory Manager</h2>

  <p>Here's where it gets interesting.</p>

  <p>Our MySQL is started with:</p>

  <pre>LD_PRELOAD=/usr/lib64/libjemalloc.so.2</pre>

  <p>jemalloc is a high-performance memory allocator that replaces glibc's malloc. It's widely used in MySQL deployments to reduce fragmentation. But jemalloc has a behavior that surprises people: <strong>it holds onto freed memory as "dirty" pages</strong>, ready to hand back to threads quickly without going back to the OS.</p>

  <p>Check if you're running jemalloc:</p>

  <pre>$ strings /proc/$(pgrep mysqld)/environ | grep LD_PRELOAD</pre>

  <p>On a busy primary, dirty pages are constantly churned and released. On an <strong>idle replica</strong> — like a DR slave not actively serving reads — jemalloc dirty pages accumulate and are never returned to the OS because there's no memory pressure to trigger eviction.</p>

  <p>We confirmed this by inspecting <code>/proc/&lt;pid&gt;/smaps</code> for dirty anonymous pages not accounted for by InnoDB mappings. The amount held by jemalloc dirty pages on our idle DR replica: approximately <strong>125 MB</strong>.</p>

  <hr>

  <h2>Layer 4: The gdb Arena Purge (Emergency Release)</h2>

  <p>To prove the jemalloc dirty-page theory and release the memory without restarting MySQL, we used a technique that feels like surgery: calling <code>mallctl</code> via gdb while MySQL was live.</p>

  <p>First, find how many arenas exist:</p>

  <pre>$ gdb -p $(pgrep mysqld) --batch \
  -ex 'call (int)mallctl("opt.narenas", 0, 0, 0, 0)'</pre>

  <p>Then purge all arenas:</p>

  <pre>$ gdb -p $(pgrep mysqld) --batch \
  -ex 'call mallctl("arena.0.purge", 0, 0, 0, 0)' \
  -ex 'call mallctl("arena.1.purge", 0, 0, 0, 0)' \
  -ex 'call mallctl("arena.2.purge", 0, 0, 0, 0)' \
  -ex 'detach'</pre>

  <p>After purging all arenas, RSS dropped by approximately <strong>125 MB</strong> — confirming jemalloc dirty pages were the source. But it also confirmed they were only ~125 MB, not the ~1,500 MB we initially suspected.</p>

  <blockquote>
    <strong>Warning:</strong> Running gdb against a live MySQL instance briefly pauses the process. Use this only on non-critical instances (DR replicas, test nodes). Never on a primary under active traffic.
  </blockquote>

  <hr>

  <h2>The Root Cause: <code>MALLOC_CONF</code> Not Set</h2>

  <p>The deeper issue wasn't the dirty pages themselves — it was that jemalloc's background decay was completely disabled.</p>

  <p>jemalloc 5.x has a <code>background_thread</code> feature: a dedicated thread that periodically purges dirty pages back to the OS on a decay schedule. Without it, dirty pages only get released when a new allocation request needs the memory.</p>

  <p>Check your jemalloc config:</p>

  <pre>$ strings /proc/$(pgrep mysqld)/environ | grep MALLOC_CONF
# if nothing returns, MALLOC_CONF is not set</pre>

  <p>On our node: <code>MALLOC_CONF</code> was <strong>not set at all</strong>. That means:</p>

  <ul>
    <li><code>background_thread = false</code> (default)</li>
    <li><code>dirty_decay_ms = 10000</code> ms — but only triggered by allocation activity</li>
    <li><code>muzzy_decay_ms = 10000</code> ms (same)</li>
  </ul>

  <p>On an idle DR replica, there's minimal allocation activity. Decay never triggers. Dirty pages accumulate indefinitely.</p>

  <hr>

  <h2>The Fixes</h2>

  <h3>Fix 1: Tune <code>innodb_buffer_pool_size</code> down slightly</h3>

  <p>Instead of 7168 MB, use <strong>6144 MB</strong> on c4d-standard-4 nodes. This is a clean multiple of the 128 MB chunk size (48 chunks), saves ~1.1 GB RSS vs. 7168 MB accounting for mmap overhead, and leaves more headroom for thread buffers.</p>

  <pre># Puppet/Hiera
percona::server::config::innodb_pool_size: 6144</pre>

  <h3>Fix 2: Reduce <code>max_connections</code></h3>

  <p>DR replicas don't serve application reads. 3,000 connections is both wasteful and an OOM risk.</p>

  <pre>max_connections = 500</pre>

  <h3>Fix 3: Enable jemalloc background_thread with decay</h3>

  <p>Add to <code>/etc/sysconfig/mysql</code> (requires mysqld restart):</p>

  <pre>MALLOC_CONF="background_thread:true,dirty_decay_ms:5000,muzzy_decay_ms:5000"</pre>

  <p>This tells jemalloc to run a dedicated background thread to decay dirty pages within 5 seconds, even on an idle process. After this change, jemalloc RSS overhead on idle replicas dropped to near zero.</p>

  <h3>Fix 4: Add Swap</h3>

  <p>Our DR nodes had <strong>zero swap</strong>. When the OOM killer fires with no swap, it's immediate and brutal — no warning, no time to respond. Even 4 GB buys you time to react.</p>

  <pre>fallocate -l 4G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' &gt;&gt; /etc/fstab</pre>

  <hr>

  <h2>The Full Memory Accounting (Before vs. After)</h2>

  <table>
    <thead>
      <tr>
        <th>Component</th>
        <th>Before</th>
        <th>After</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>InnoDB buffer pool (configured)</td>
        <td>7,168 MB</td>
        <td>6,144 MB</td>
      </tr>
      <tr>
        <td>InnoDB mmap chunk overhead (~9%)</td>
        <td>~645 MB</td>
        <td>~553 MB</td>
      </tr>
      <tr>
        <td>InnoDB internals (AHI, log buffer, etc.)</td>
        <td>~300 MB</td>
        <td>~300 MB</td>
      </tr>
      <tr>
        <td>Performance Schema</td>
        <td>~150 MB</td>
        <td>~150 MB</td>
      </tr>
      <tr>
        <td>Per-thread buffers</td>
        <td>~2,000 MB (3000 conns)</td>
        <td>~500 MB (500 conns)</td>
      </tr>
      <tr>
        <td>jemalloc dirty pages</td>
        <td>~125 MB (accumulating)</td>
        <td>~50 MB (background decay)</td>
      </tr>
      <tr>
        <td>OS + binary + misc</td>
        <td>~200 MB</td>
        <td>~200 MB</td>
      </tr>
      <tr>
        <td>Total mysqld RSS</td>
        <td>~10,490 MB</td>
        <td>~7,897 MB</td>
      </tr>
    </tbody>
  </table>

  <hr>

  <h2>Key Takeaways</h2>

  <ol>
    <li><strong>mysqld RSS ≈ buffer_pool × 1.09 + everything else.</strong> The 9% InnoDB mmap overhead is real and underdocumented.</li>
    <li><strong><code>max_connections</code> is memory, not just a connection limit.</strong> 3,000 connections × ~1 MB THD overhead = 3 GB potential. Size it for actual use.</li>
    <li><strong>jemalloc on idle replicas accumulates dirty pages.</strong> If <code>MALLOC_CONF</code> isn't set, <code>background_thread</code> is off and decay only happens during allocation pressure. Fix it with <code>background_thread:true</code>.</li>
    <li><strong>gdb arena purge is a valid diagnostic tool</strong> on non-critical instances — but it's a band-aid. Fix <code>MALLOC_CONF</code> properly.</li>
    <li><strong>No swap = no warning before OOM.</strong> Even 4 GB buys you response time before the OOM killer fires.</li>
    <li><strong>Check <code>/proc/&lt;pid&gt;/smaps</code></strong> when RSS is mysterious. It shows every memory region with sizes and dirty-page counts.</li>
  </ol>

  <hr>

  <h2>Quick Diagnostic Checklist</h2>

  <pre># 1. Current mysqld RSS
ps -o rss= -p $(pgrep mysqld) | awk '{print $1/1024 " MB"}'

# 2. Buffer pool size
mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size'"

# 3. Are you running jemalloc?
strings /proc/$(pgrep mysqld)/environ | grep LD_PRELOAD

# 4. Is MALLOC_CONF set?
strings /proc/$(pgrep mysqld)/environ | grep MALLOC_CONF

# 5. Max connections configured vs. peak usage
mysql -e "SHOW VARIABLES LIKE 'max_connections'; SHOW STATUS LIKE 'Max_used_connections'"

# 6. Swap availability
free -h

# 7. smaps summary (requires root)
awk '/^Rss:/{r+=$2} /^Dirty:/{d+=$2} END{print "RSS:", r/1024, "MB | Dirty:", d/1024, "MB"}' \
  /proc/$(pgrep mysqld)/smaps</pre>

  <hr>

  <p>If this helped you track down a memory mystery, drop a comment below. MySQL memory accounting is genuinely complex — the more we share real debugging stories, the better the community gets at it.</p>

</div>]]></content:encoded>
    <pubDate>Mon, 31 Aug 2026 10:10:11 +0000</pubDate>
    <dc:creator>Ankit Kumar</dc:creator>
    <category>GCP</category>
    <category>InnoDB</category>
    <category>jemalloc</category>
    <category>Memory Tuning</category>
    <category>MySQL</category>
    <category>Performance</category>
  </item>

  <item>
    <title>Install MySQL on macOS with Homebrew</title>
    <guid isPermaLink="false">https://codeforgeek.com/?p=6974</guid>
    <link>https://codeforgeek.com/how-to-install-mysql-on-macos/</link>
    <description>Homebrew gives you the shortest path to a local MySQL server on macOS, but an install command alone does not prove that the server is running or that the client can connect. I checked the Homebrew mysql formula and built this sequence around the package, service, security, and connection checks it documents. Use this route […]</description>
    <pubDate>Mon, 31 Aug 2026 04:33:03 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Database</category>
    <category>Mysql</category>
  </item>

  <item>
    <title>Connect Deno to MySQL with mysql2</title>
    <guid isPermaLink="false">https://codeforgeek.com/?p=6972</guid>
    <link>https://codeforgeek.com/deno-and-mysql-connection-tutorial/</link>
    <description>Deno can use the mysql2 driver through its npm compatibility layer, so a Deno app can open a MySQL connection without reviving an older URL import. The working path is small: give the app a limited database account, read its connection values from environment variables, execute a parameterized query, then close the pool. I ran […]</description>
    <pubDate>Sat, 29 Aug 2026 09:12:25 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Database</category>
    <category>Mysql</category>
  </item>

  <item>
    <title>MySQL Release Models: LTS, Innovation, and Single Release</title>
    <guid isPermaLink="false">5fafc37173e09b58a5857570535f3978</guid>
    <link>https://blogs.oracle.com/mysql/mysql-release-models-lts-innovation-and-single-release</link>
    <description>What LTS, Innovation, and “latest supported” mean across MySQL Server, Shell, Router, Connectors, and Operator MySQL has two different release models, and understanding which products follow which model makes choosing versions much simpler. MySQL Server and MySQL NDB Cluster offer Long-Term Support (LTS) and Innovation releases. MySQL Shell, MySQL Router, MySQL Connectors, and MySQL Operator for Kubernetes follow a Single Releasemodel: use […]</description>
    <pubDate>Fri, 28 Aug 2026 18:03:51 +0000</pubDate>
    <dc:creator>Oracle MySQL Group</dc:creator>
    <category>MySQL</category>
    <category>MySQL Community</category>
    <category>MySQL Enterprise</category>
    <category>MySQL HeatWave</category>
    <category>MySQL NDB</category>
    <category>Calendar Versioning</category>
    <category>innovation</category>
    <category>lts</category>
    <category>mysql</category>
    <category>MySQL Connectors</category>
    <category>MySQL Operator for Kubernetes</category>
    <category>MySQL Router</category>
    <category>MySQL Server</category>
    <category>MySQL Shell</category>
  </item>

  <item>
    <title>Build a Deno API Server with MySQL</title>
    <guid isPermaLink="false">https://codeforgeek.com/?p=6960</guid>
    <link>https://codeforgeek.com/building-api-server-using-deno-and-mysql/</link>
    <description>Build a Deno API server with MySQL by keeping HTTP handling separate from database work. The handler below rejects an empty name, creates a user, and lists users, while MySQL2 owns parameterized SQL. Install Deno and prepare MySQL Install Deno with the current Deno installation instructions. Deno can import npm packages, including MySQL2, through its […]</description>
    <pubDate>Fri, 28 Aug 2026 09:14:02 +0000</pubDate>
    <dc:creator>Shahid shaikh</dc:creator>
    <category>Database</category>
    <category>Mysql</category>
  </item>

  <item>
    <title>MySQL 26.7 – Thank you for your contributions!</title>
    <guid isPermaLink="false">0b250c5cff2b88993e0a8ec7d24bd891</guid>
    <link>https://blogs.oracle.com/mysql/mysql-26-7-thank-you-for-your-contributions</link>
    <description>The MySQL team would like to thank everyone in the community who contributed bug reports, patches, pull requests, and continued feedback that became part of the MySQL 26.7 release. As always, community contributions help make MySQL better for everyone. In this release, we received contributions across the MySQL Server Optimizer, Parser, Prepared Statements, Performance Schema, […]</description>
    <pubDate>Thu, 27 Aug 2026 17:42:53 +0000</pubDate>
    <dc:creator>Oracle MySQL Group</dc:creator>
    <category>MySQL</category>
    <category>MySQL Community</category>
    <category>mysql</category>
    <category>MySQL Contributions</category>
    <category>mysqlcommunity</category>
  </item>

  <item>
    <title>IPv6-ready applications with MySQL HeatWave on OCI</title>
    <guid isPermaLink="false">be0365475afdd2dbbe51cff26fad2bca</guid>
    <link>https://blogs.oracle.com/mysql/ipv6-ready-applications-with-mysql-heatwave-on-oci</link>
    <description>Users can now provision a DB system with an IPv6 address when the selected subnet supports IPv6. Applications can then reach the DB system through its IPv6 primary endpoint. IPv6 connectivity, familiar MySQL Modern cloud applications increasingly use IPv6 to expand address capacity, simplify large network estates, and support long-term infrastructure modernization. Yet a single […]</description>
    <pubDate>Thu, 27 Aug 2026 15:24:09 +0000</pubDate>
    <dc:creator>Oracle MySQL Group</dc:creator>
    <category>MySQL</category>
    <category>MySQL HeatWave</category>
    <category>News</category>
    <category>IPv6</category>
    <category>mysql</category>
    <category>networking</category>
    <category>OCI</category>
  </item>

  <item>
    <title>Performance Progression of Percona Server for MySQL 8.4</title>
    <guid isPermaLink="false">https://www.percona.com/?p=52466</guid>
    <link>https://www.percona.com/blog/performance-progression-of-percona-server-for-mysql-8-4/</link>
    <description>1. Purpose and scope
This performance investigation aims to look into the read/write performance of Percona Server for MySQL 8.4 and how it changed between versions released in 2026:

8.4.8-8 released on 12 March 2026
8.4.10-10 released on 30 June 2026
8.4.11-11 released on 20 August 2026

We want to see if there are improvements in scalability and performance in OLTP read/write operations, where the improvements are most noticeable and how they were achieved. For some readers this material might help with making the decision whether upgrading to a newer version is worth the effort.
An important note is that the new features or security patches will not be taken into consideration.
Measuring Latency (Percentiles) and Resource Utilization (CPU, RAM, I/O) is not in the scope of this post.
 
2. Configuration and Methodology
The configuration was as follows:



Benchmark
Sysbench OLTP Read-Write


CPU
Intel Xeon Gold 6230 (2×20 cores, HT = 80 logical CPUs)


RAM
187 GiB DDR4


Storage
NVMe SSD (2.9 TB) INTEL SSDPE2KE032T8


OS
Ubuntu 24.04, kernel 6.8.0-60-generic


DB Engines
Percona Server for MySQL 8.4.8-8 (release build)
Percona Server for MySQL 8.4.10-10 (release build)Percona Server for MySQL 8.4.11-11 (release build)



The benchmarks were done across the following dimensions:



Database Sizes (Row Number)
24Gb (100M rows) / 48Gb (200M rows) / 96Gb (400M rows)


Number of tables in DB Schema
20 (this number is constant for all runs)
Database Schema definition can be downloaded from here: 
https://percona-lab-results.github.io/2026-interactive-metrics/schema_dump.sql


Number of concurrent threads
1 / 4 / 16 / 32 / 64 / 128 / 256 / 512


Buffer to Data Ratio
1:12 (I/O bound), 1:2 (Partially buffered), 1:1 (Fully buffered)



One of the points in benchmarking was to create combinations of similar Buffer to Data Ratios, but with the different Database Sizes. This gives us the following possible combinations of innodb_buffer_pool_size and Database Size:



1:12 (I/O bound)
innodb_buffer_pool_size = 2G, Data Size = 24Gb
innodb_buffer_pool_size = 4G, Data Size = 48Gb
innodb_buffer_pool_size = 8G, Data Size = 96Gb


1:2 (Partially buffered)
innodb_buffer_pool_size = 12G, Data Size = 24Gb
innodb_buffer_pool_size = 24G, Data Size = 48Gb
innodb_buffer_pool_size = 48G, Data Size = 96Gb


1:1 (Fully buffered)
innodb_buffer_pool_size = 32G, Data Size = 24Gb
innodb_buffer_pool_size = 64G, Data Size = 48Gb
innodb_buffer_pool_size = 128G, Data Size = 96Gb



We should be able to see how efficiently the server manages an increasingly larger number of rows while keeping the Buffer to Data Ratio the same.
Execution of the benchmarks was done as follows:



Ramp-up
24G – 600 sec (10 min) – could be shorter
48G – 600 sec (10 min)96G – 900 sec (15 min)

The Ramp-up times were established experimentally depending on the Data Size until the point when increasing them further did not bring significant changes.


Measurement window
900 sec (15 min)

Ideally it should be as long as possible, but measurements should take reasonable time. Hence, we used the experience of previous benchmarks and established that this window is adequate for the purpose.


Number of runs
3
For each combination there are multiple runs.
The interactive graph can show data for individual runs as well as averaged value.



Important Database Configuration options (the actual config files with specific settings for each run can be downloaded from the interactive graphs):



InnoDB – Buffer pool Tier


innodb_buffer_pool_size
2G/4G/8G/12G/24G/32G/48G/64G/128G


innodb_buffer_pool_load_at_startup
OFF


innodb_buffer_pool_dump_at_shutdown
OFF


Thread Pool


thread_handling
pool-of-threads


thread_pool_size
80 # match physical core count


thread_pool_max_threads
2000


thread_pool_oversubscribe
3


Threading


thread_stack
512K


thread_cache_size
256


back_log
4096


InnoDB I/O


innodb_io_capacity
10000


innodb_io_capacity_max
20000


innodb_read_io_threads
16


innodb_write_io_threads
16


innodb_use_native_aio
ON


InnoDB Log / Durability


innodb_log_buffer_size
256M


innodb_flush_log_at_trx_commit
1 # full ACID


innodb_doublewrite
ON


InnoDB – Concurrency &amp;amp; OLTP Tuning


innodb_stats_on_metadata
OFF


innodb_open_files
65536


innodb_lock_wait_timeout
50


innodb_rollback_on_timeout
ON


Per-Session Buffers


sort_buffer_size    
4M


join_buffer_size    
4M


read_buffer_size    
2M


read_rnd_buffer_size
4M


tmp_table_size      
256M


max_heap_table_size
256M


Binary Log


disable_log_bin
ON # Disabled binlog


Other InnoDB settings


innodb_redo_log_capacity    
4G


innodb_change_buffering     
none


innodb_flush_method         
O_DIRECT


innodb_buffer_pool_instances
Calculated as
(innodb_buffer_pool_size G / 5)
But must be in range [1..8]


Misc server settings


collation_server
utf8mb4_unicode_ci


bulk_insert_buffer_size
256M


myisam_sort_buffer_size 
128M


key_buffer_size         
64M # MyISAM only, keep small for OLTP



In the high concurrency scenario when all CPU cores are working under maximum load the performance fluctuations might appear out of the ability of a specific CPU crystal to work at a specific sustainable maximum frequency. Intel Xeon Gold 6230 processors installed in the test servers have a base frequency of 2100 MHz and maximum turbo frequency of 3900 MHz. However, such turbo frequency can only be achieved for a short period of time on an isolated core. The load and the heat production of the physical core neighbours limit the frequency of the whole CPU. Some CPU’s were able to hold 2530 MHz on all cores for 20+ hours of intense load, others could only reach 2420 MHz. For consistency of the tests the turbo frequency was capped to 2400 MHz from the beginning on all servers. It helped to eliminate the struggle between turbo mode trying to increase the frequency beyond sustainable levels and the CPU thermal protection bringing the clock down. More stable hardware performance reduced the measurement fluctuations during the benchmark runs regardless if they were done on the same or a different physical server.
 
3. Results
First, let’s check the I/O bound scenario where the InnoDB Buffer to Data Size is the smallest (1:12).
The graph shows the configurations with innodb_buffer_pool_size=8G and Data Size 96G (or 20M rows per table, 400M rows in total):

[ INTERACTIVE GRAPH ][ TABLE ]
The first thing that catches the eye is the hugely superior performance of the version 8.4.11-11 over 8.4.10-10 and 8.4.8-8 in the high thread numbers. In the situations when the number of physical cores (80) is smaller than the number of threads (128+) the versions 8.4.10-10 and 8.4.8-8 have a steep performance degradation. However, the TPS for 8.4.11-11 keeps growing. This is due to the optimization done to InnoDB LRU pages flushing algorithm. The optimization specifically targeted the scenario when the data size is larger than the available server buffers and the server has many concurrent connections doing random read-write operations. The optimizations in 8.4.11-11 deserve a separate explanation and they will be published in another blog post.

The less noticeable, but important difference can be spotted between the TPS for 8.4.8-8 and 8.4.10-10.
The version 8.4.10-10 shows better performance (especially at the saturation point with 64 threads), which should mostly be attributed to the introduction of Performance Guided Optimization (PGO). 
More information on PGO can be found here:
https://docs.percona.com/percona-server/8.4/pgo.html
With the smaller data and buffer sizes the performance difference gives an almost identical picture:



4G buffer, 48G data [ INTERACTIVE GRAPH ][ TABLE ]
2G buffer, 24G data [ INTERACTIVE GRAPH ][ TABLE ]



Now let’s review what happens with the ratio 1:2.
This time the buffer pool size also plays a more significant role and the performance difference is not characterized by the Buffer / Data size ratio.
With innodb_buffer_pool_size=12G and 24G data size the performance gap between 8.4.11-11 and older versions is still huge as can be seen on the graph:

[ INTERACTIVE GRAPH ][ TABLE ]
However, setting innodb_buffer_pool_size=24G and 48G data size reduces the gap. The superiority of 8.4.11-11 is still visible:

[ INTERACTIVE GRAPH ][ TABLE ]
Moving to innodb_buffer_pool_size=48G and 96G data size shrinks the gap even more:

[ INTERACTIVE GRAPH ][ TABLE ]
In this post we are not going to talk about mechanisms behind shrinking performance gaps in 1:2 Buffer / Data size ratio.
Holding the entire data set in memory is not the most common thing for the database server, but in some cases it happens. Therefore, we are covering such situations as well.

[ INTERACTIVE GRAPH ][ TABLE ]
As the above graph shows, 8.4.10-10 is slightly ahead of 8.4.11-11, but the gap is very small.
This behavior is consistent with other data sizes for fully buffered data:
innodb_buffer_pool_size=64G and 48G Data Size:

[ INTERACTIVE GRAPH ][ TABLE ]
innodb_buffer_pool_size=128G and 96G Data Size:

[ INTERACTIVE GRAPH ][ TABLE ]
Again, we will not go into details about why this happens. Though it is worth noting that both 8.4.10-10 and 8.4.11-11 do better than 8.4.8-8 in all runs and configurations.
The table interpretation of the results is available as well.
 
4. Comparing with Upstream MySQL 8.4.11.
The performance improvements in Percona Server for MySQL 8.4.11-11 are not a part of the Upstream MySQL 8.4.11. The patch was specifically designed to address the issue of Percona Server being slower than MySQL in I/O bound scenarios.
Also, the patch eliminated the abrupt performance degradation in the higher thread count after reaching the saturation point at 64 threads:

[ INTERACTIVE GRAPH ][ TABLE ]
As the graph shows – Percona Server 8.4.8-8 / 8.4.10-10 was slower than MySQL in lower thread count. Although it was still faster in 128+ threads, the Percona Server was still subject to a substantial slow-down. That is where Percona Server 8.4.11-11 really shines.
However, with the fully buffered data MySQL goes faster than any Percona Server:

[ INTERACTIVE GRAPH ][ TABLE ]
5. Summary
The Performance of the Percona Server 8.4 for MySQL is progressing well from older to newer version offering significant performance improvements especially in the version 8.4.11-11. This version shows very significant improvements in performance on the data sets that require I/O. Also, it outperformed the upstream MySQL 8.4.11.
With fully buffered data sets the version 8.4.10-10 is slightly better than 8.4.11-11. MySQL Server in this case shows the fastest performance.
The PGO had a positive impact demonstrating the version 8.4.10-10 being faster in all tests on all configurations than 8.4.8-8.
The performance depends not only on the ratio between the buffer and the data size, but also on the buffer size.
The post Performance Progression of Percona Server for MySQL 8.4 appeared first on Percona.</description>
    <content:encoded><![CDATA[<h2><span>1. Purpose and scope</span></h2>
<p><span>This performance investigation aims to look into the read/write performance of Percona Server for MySQL 8.4 and how it changed between versions released in 2026:</span></p>
<ul>
<li aria-level="1"><span>8.4.8-8 released on 12 March 2026</span></li>
<li aria-level="1"><span>8.4.10-10 released on 30 June 2026</span></li>
<li aria-level="1"><span>8.4.11-11 released on 20 August 2026</span></li>
</ul>
<p><span>We want to see if there are improvements in scalability and performance in OLTP read/write operations, where the improvements are most noticeable and how they were achieved. For some readers this material might help with making the decision whether upgrading to a newer version is worth the effort.</span></p>
<p><span>An important note is that the new features or security patches will not be taken into consideration.</span></p>
<p><span>Measuring Latency (Percentiles) and Resource Utilization (CPU, RAM, I/O) is not in the scope of this post.</span></p>
<p> </p>
<h2><span>2. Configuration and Methodology</span></h2>
<p><span>The configuration was as follows:</span></p>
<table border="1" width="100%" cellpadding="5">
<tbody>
<tr>
<td><span>Benchmark</span></td>
<td><span>Sysbench OLTP Read-Write</span></td>
</tr>
<tr>
<td><span>CPU</span></td>
<td><span>Intel Xeon Gold 6230 (2×20 cores, HT = 80 logical CPUs)</span></td>
</tr>
<tr>
<td><span>RAM</span></td>
<td><span>187 GiB DDR4</span></td>
</tr>
<tr>
<td><span>Storage</span></td>
<td><span>NVMe SSD (2.9 TB) INTEL SSDPE2KE032T8</span></td>
</tr>
<tr>
<td><span>OS</span></td>
<td><span>Ubuntu 24.04, kernel 6.8.0-60-generic</span></td>
</tr>
<tr>
<td><span>DB Engines</span></td>
<td><span>Percona Server for MySQL 8.4.8-8 (release build)</span><span><br>
</span><span>Percona Server for MySQL 8.4.10-10 (release build)</span><span>Percona Server for MySQL 8.4.11-11 (release build)</span></td>
</tr>
</tbody>
</table>
<p><span>The benchmarks were done across the following dimensions:</span></p>
<table border="1" width="100%" cellpadding="5">
<tbody>
<tr>
<td><span>Database Sizes (Row Number)</span></td>
<td><span>24Gb (100M rows) / 48Gb (200M rows) / 96Gb (400M rows)</span></td>
</tr>
<tr>
<td><span>Number of tables in DB Schema</span></td>
<td><span>20 (this number is constant for all runs)</span>
<p><span>Database Schema definition can be downloaded from here: </span></p>
<p><a href="https://percona-lab-results.github.io/2026-interactive-metrics/schema_dump.sql" target="_blank" rel="noopener"><span>https://percona-lab-results.github.io/2026-interactive-metrics/schema_dump.sql</span></a></p></td>
</tr>
<tr>
<td><span>Number of concurrent threads</span></td>
<td><span>1 / 4 / 16 / 32 / 64 / 128 / 256 / 512</span></td>
</tr>
<tr>
<td><span>Buffer to Data Ratio</span></td>
<td><span>1:12 (I/O bound), 1:2 (Partially buffered), 1:1 (Fully buffered)</span></td>
</tr>
</tbody>
</table>
<p><span>One of the points in benchmarking was to create combinations of similar Buffer to Data Ratios, but with the different Database Sizes. This gives us the following possible combinations of </span><span>innodb_buffer_pool_size</span><span> and Database Size:</span></p>
<table border="1" width="100%" cellpadding="5">
<tbody>
<tr>
<td><span>1:12 (I/O bound)</span></td>
<td><span>innodb_buffer_pool_size = 2G, Data Size = </span><span>24Gb</span><span><br>
</span><span>innodb_buffer_pool_size = 4G, Data Size = </span><span>48Gb</span><span><br>
</span><span>innodb_buffer_pool_size = 8G, Data Size = </span><span>96Gb</span></td>
</tr>
<tr>
<td><span>1:2 (Partially buffered)</span></td>
<td><span>innodb_buffer_pool_size = 12G, Data Size = </span><span>24Gb</span><span><br>
</span><span>innodb_buffer_pool_size = 24G, Data Size = </span><span>48Gb</span><span><br>
</span><span>innodb_buffer_pool_size = 48G, Data Size = </span><span>96Gb</span></td>
</tr>
<tr>
<td><span>1:1 (Fully buffered)</span></td>
<td><span>innodb_buffer_pool_size = 32G, Data Size = </span><span>24Gb</span><span><br>
</span><span>innodb_buffer_pool_size = 64G, Data Size = </span><span>48Gb</span><span><br>
</span><span>innodb_buffer_pool_size = 128G, Data Size = </span><span>96Gb</span></td>
</tr>
</tbody>
</table>
<p><span>We should be able to see how efficiently the server manages an increasingly larger number of rows while keeping the Buffer to Data Ratio the same.</span></p>
<p><span>Execution of the benchmarks was done as follows:</span></p>
<table border="1" width="100%" cellpadding="5">
<tbody>
<tr>
<td><span>Ramp-up</span></td>
<td><b>24G – 600 sec (10 min)</b><span> – could be shorter</span><span><br>
</span><b>48G – 600 sec (10 min)</b><b>96G – 900 sec (15 min)</b><span><br>
</span><span><br>
</span><span>The Ramp-up times were established experimentally depending on the Data Size until the point when increasing them further did not bring significant changes.</span></td>
</tr>
<tr>
<td><span>Measurement window</span></td>
<td><b>900 sec (15 min)</b><span><br>
</span><span><br>
</span><span>Ideally it should be as long as possible, but measurements should take reasonable time. Hence, we used the experience of previous benchmarks and established that this window is adequate for the purpose.</span></td>
</tr>
<tr>
<td><span>Number of runs</span></td>
<td><b>3</b>
<p><span>For each combination there are multiple runs.</span><span><br>
</span><span>The interactive graph can show data for individual runs as well as averaged value.</span></p></td>
</tr>
</tbody>
</table>
<p><span>Important Database Configuration options (the actual config files with specific settings for each run can be downloaded from the interactive graphs):</span></p>
<table border="1" width="100%" cellpadding="5">
<tbody>
<tr bgcolor="#DDDDDD">
<td colspan="2"><b>InnoDB – Buffer pool Tier</b></td>
</tr>
<tr>
<td><span>innodb_buffer_pool_size</span></td>
<td><b>2G/4G/8G/12G/24G/32G/48G/64G/128G</b></td>
</tr>
<tr>
<td><span>innodb_buffer_pool_load_at_startup</span></td>
<td><span>OFF</span></td>
</tr>
<tr>
<td><span>innodb_buffer_pool_dump_at_shutdown</span></td>
<td><span>OFF</span></td>
</tr>
<tr bgcolor="#DDDDDD">
<td colspan="2"><b>Thread Pool</b></td>
</tr>
<tr>
<td><span>thread_handling</span></td>
<td><span>pool-of-threads</span></td>
</tr>
<tr>
<td><span>thread_pool_size</span></td>
<td><span>80 # match physical core count</span></td>
</tr>
<tr>
<td><span>thread_pool_max_threads</span></td>
<td><span>2000</span></td>
</tr>
<tr>
<td><span>thread_pool_oversubscribe</span></td>
<td><span>3</span></td>
</tr>
<tr bgcolor="#DDDDDD">
<td colspan="2"><b>Threading</b></td>
</tr>
<tr>
<td><span>thread_stack</span></td>
<td><span>512K</span></td>
</tr>
<tr>
<td><span>thread_cache_size</span></td>
<td><span>256</span></td>
</tr>
<tr>
<td><span>back_log</span></td>
<td><span>4096</span></td>
</tr>
<tr bgcolor="#DDDDDD">
<td colspan="2"><b>InnoDB I/O</b></td>
</tr>
<tr>
<td><span>innodb_io_capacity</span></td>
<td><span>10000</span></td>
</tr>
<tr>
<td><span>innodb_io_capacity_max</span></td>
<td><span>20000</span></td>
</tr>
<tr>
<td><span>innodb_read_io_threads</span></td>
<td><span>16</span></td>
</tr>
<tr>
<td><span>innodb_write_io_threads</span></td>
<td><span>16</span></td>
</tr>
<tr>
<td><span>innodb_use_native_aio</span></td>
<td><span>ON</span></td>
</tr>
<tr bgcolor="#DDDDDD">
<td colspan="2"><b>InnoDB Log / Durability</b></td>
</tr>
<tr>
<td><span>innodb_log_buffer_size</span></td>
<td><span>256M</span></td>
</tr>
<tr>
<td><span>innodb_flush_log_at_trx_commit</span></td>
<td><span>1 # full ACID</span></td>
</tr>
<tr>
<td><span>innodb_doublewrite</span></td>
<td><span>ON</span></td>
</tr>
<tr bgcolor="#DDDDDD">
<td colspan="2"><b>InnoDB – Concurrency &amp; OLTP Tuning</b></td>
</tr>
<tr>
<td><span>innodb_stats_on_metadata</span></td>
<td><span>OFF</span></td>
</tr>
<tr>
<td><span>innodb_open_files</span></td>
<td><span>65536</span></td>
</tr>
<tr>
<td><span>innodb_lock_wait_timeout</span></td>
<td><span>50</span></td>
</tr>
<tr>
<td><span>innodb_rollback_on_timeout</span></td>
<td><span>ON</span></td>
</tr>
<tr bgcolor="#DDDDDD">
<td colspan="2"><b>Per-Session Buffers</b></td>
</tr>
<tr>
<td><span>sort_buffer_size    </span></td>
<td><span>4M</span></td>
</tr>
<tr>
<td><span>join_buffer_size    </span></td>
<td><span>4M</span></td>
</tr>
<tr>
<td><span>read_buffer_size    </span></td>
<td><span>2M</span></td>
</tr>
<tr>
<td><span>read_rnd_buffer_size</span></td>
<td><span>4M</span></td>
</tr>
<tr>
<td><span>tmp_table_size      </span></td>
<td><span>256M</span></td>
</tr>
<tr>
<td><span>max_heap_table_size</span></td>
<td><span>256M</span></td>
</tr>
<tr bgcolor="#DDDDDD">
<td colspan="2"><b>Binary Log</b></td>
</tr>
<tr>
<td><span>disable_log_bin</span></td>
<td><span>ON # Disabled binlog</span></td>
</tr>
<tr bgcolor="#DDDDDD">
<td colspan="2"><b>Other InnoDB settings</b></td>
</tr>
<tr>
<td><span>innodb_redo_log_capacity    </span></td>
<td><span>4G</span></td>
</tr>
<tr>
<td><span>innodb_change_buffering     </span></td>
<td><span>none</span></td>
</tr>
<tr>
<td><span>innodb_flush_method         </span></td>
<td><span>O_DIRECT</span></td>
</tr>
<tr>
<td><span>innodb_buffer_pool_instances</span></td>
<td><b>Calculated as</b><b><br>
</b><b>(innodb_buffer_pool_size G / 5)</b><b><br>
</b><b>But must be in range [1..8]</b></td>
</tr>
<tr bgcolor="#DDDDDD">
<td colspan="2"><b>Misc server settings</b></td>
</tr>
<tr>
<td><span>collation_server</span></td>
<td><span>utf8mb4_unicode_ci</span></td>
</tr>
<tr>
<td><span>bulk_insert_buffer_size</span></td>
<td><span>256M</span></td>
</tr>
<tr>
<td><span>myisam_sort_buffer_size </span></td>
<td><span>128M</span></td>
</tr>
<tr>
<td><span>key_buffer_size         </span></td>
<td><span>64M # MyISAM only, keep small for OLTP</span></td>
</tr>
</tbody>
</table>
<p><span>In the high concurrency scenario when all CPU cores are working under maximum load the performance fluctuations might appear out of the ability of a specific CPU crystal to work at a specific sustainable maximum frequency. Intel Xeon Gold 6230 processors installed in the test servers have a base frequency of 2100 MHz and maximum turbo frequency of 3900 MHz. However, such turbo frequency can only be achieved for a short period of time on an isolated core. The load and the heat production of the physical core neighbours limit the frequency of the whole CPU. Some CPU’s were able to hold 2530 MHz on all cores for 20+ hours of intense load, others could only reach 2420 MHz. For consistency of the tests the turbo frequency was capped to 2400 MHz from the beginning on all servers. It helped to eliminate the struggle between turbo mode trying to increase the frequency beyond sustainable levels and the CPU thermal protection bringing the clock down. More stable hardware performance reduced the measurement fluctuations during the benchmark runs regardless if they were done on the same or a different physical server.</span></p>
<p> </p>
<h2><span>3. Results</span></h2>
<p><span>First, let’s check the I/O bound scenario where the InnoDB Buffer to Data Size is the smallest (1:12).</span></p>
<p><span>The graph shows the configurations with </span><span>innodb_buffer_pool_size</span><span>=8G and Data Size 96G (or 20M rows per table, 400M rows in total):</span></p>
<p><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=8" target="_blank" rel="noopener"><img decoding="async" class="alignnone size-full wp-image-52471" src="https://www.percona.com/wp-content/uploads/2026/08/graph-8g.png" alt="" width="1013" height="621" srcset="https://www.percona.com/wp-content/uploads/2026/08/graph-8g.png 1013w, https://www.percona.com/wp-content/uploads/2026/08/graph-8g-300x184.png 300w, https://www.percona.com/wp-content/uploads/2026/08/graph-8g-768x471.png 768w" sizes="(max-width: 1013px) 100vw, 1013px"></a><br>
<span>[ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=8" target="_blank" rel="noopener"><span>INTERACTIVE GRAPH</span></a><span> ][ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=table&amp;mem=8" target="_blank" rel="noopener"><span>TABLE</span></a><span> ]</span></p>
<p><span>The first thing that catches the eye is the hugely superior performance of the version 8.4.11-11 over 8.4.10-10 and 8.4.8-8 in the high thread numbers. In the situations when the number of physical cores (80) is smaller than the number of threads (128+) the versions 8.4.10-10 and 8.4.8-8 have a steep performance degradation. However, the TPS for 8.4.11-11 keeps growing. This is due to the optimization done to InnoDB LRU pages flushing algorithm. The optimization specifically targeted the scenario when the data size is larger than the available server buffers and the server has many concurrent connections doing random read-write operations. The optimizations in 8.4.11-11 deserve a separate explanation and they will be published in another blog post.</span><span><br>
</span></p>
<p><span>The less noticeable, but important difference can be spotted between the TPS for 8.4.8-8 and 8.4.10-10.</span></p>
<p><span>The version 8.4.10-10 shows better performance (especially at the saturation point with 64 threads), which should mostly be attributed to the introduction of Performance Guided Optimization (PGO). </span></p>
<p><span>More information on PGO can be found here:</span></p>
<p><a href="https://docs.percona.com/percona-server/8.4/pgo.html"><span>https://docs.percona.com/percona-server/8.4/pgo.html</span></a></p>
<p><span>With the smaller data and buffer sizes the performance difference gives an almost identical picture:</span></p>
<table border="1" width="100%" cellpadding="5">
<tbody>
<tr>
<td width="50%"><span>4G buffer, 48G data </span><span>[ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=4" target="_blank" rel="noopener"><span>INTERACTIVE GRAPH</span></a><span> ][ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=table&amp;mem=4" target="_blank" rel="noopener"><span>TABLE</span></a><span> ]</span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=4" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="alignnone wp-image-52475 size-full" src="https://www.percona.com/wp-content/uploads/2026/08/graph-4g.png" alt="" width="1007" height="623" srcset="https://www.percona.com/wp-content/uploads/2026/08/graph-4g.png 1007w, https://www.percona.com/wp-content/uploads/2026/08/graph-4g-300x186.png 300w, https://www.percona.com/wp-content/uploads/2026/08/graph-4g-768x475.png 768w" sizes="auto, (max-width: 1007px) 100vw, 1007px"></a></td>
<td width="50%"><span>2G buffer, 24G data </span><span>[ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=2" target="_blank" rel="noopener"><span>INTERACTIVE GRAPH</span></a><span> ][ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=table&amp;mem=2" target="_blank" rel="noopener"><span>TABLE</span></a><span> ]</span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=2" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="alignnone wp-image-52474 size-full" src="https://www.percona.com/wp-content/uploads/2026/08/graph-2g.png" alt="" width="1013" height="623" srcset="https://www.percona.com/wp-content/uploads/2026/08/graph-2g.png 1013w, https://www.percona.com/wp-content/uploads/2026/08/graph-2g-300x185.png 300w, https://www.percona.com/wp-content/uploads/2026/08/graph-2g-768x472.png 768w" sizes="auto, (max-width: 1013px) 100vw, 1013px"></a></td>
</tr>
</tbody>
</table>
<p><span>Now let’s review what happens with the ratio 1:2.</span><span><br>
</span><span>This time the buffer pool size also plays a more significant role and the performance difference is not characterized by the Buffer / Data size ratio.</span></p>
<p><span>With </span><span>innodb_buffer_pool_size</span><span>=12G and 24G data size the performance gap between 8.4.11-11 and older versions is still huge as can be seen on the graph:</span></p>
<p><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=12" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-52481" src="https://www.percona.com/wp-content/uploads/2026/08/graph-12g.png" alt="" width="1012" height="624" srcset="https://www.percona.com/wp-content/uploads/2026/08/graph-12g.png 1012w, https://www.percona.com/wp-content/uploads/2026/08/graph-12g-300x185.png 300w, https://www.percona.com/wp-content/uploads/2026/08/graph-12g-768x474.png 768w" sizes="auto, (max-width: 1012px) 100vw, 1012px"></a><br>
<span>[ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=12" target="_blank" rel="noopener"><span>INTERACTIVE GRAPH</span></a><span> ][ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=table&amp;mem=12" target="_blank" rel="noopener"><span>TABLE</span></a><span> ]</span></p>
<p><span>However, setting </span><span>innodb_buffer_pool_size</span><span>=24G and 48G data size reduces the gap. The superiority of 8.4.11-11 is still visible:</span></p>
<p><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=12" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-52482" src="https://www.percona.com/wp-content/uploads/2026/08/graph-24g.png" alt="" width="1012" height="622" srcset="https://www.percona.com/wp-content/uploads/2026/08/graph-24g.png 1012w, https://www.percona.com/wp-content/uploads/2026/08/graph-24g-300x184.png 300w, https://www.percona.com/wp-content/uploads/2026/08/graph-24g-768x472.png 768w" sizes="auto, (max-width: 1012px) 100vw, 1012px"></a><br>
<span>[ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=24" target="_blank" rel="noopener"><span>INTERACTIVE GRAPH</span></a><span> ][ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=table&amp;mem=24" target="_blank" rel="noopener"><span>TABLE</span></a><span> ]</span></p>
<p><span>Moving to </span><span>innodb_buffer_pool_size</span><span>=48G and 96G data size shrinks the gap even more:</span></p>
<p><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=48" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-52485" src="https://www.percona.com/wp-content/uploads/2026/08/graph-48g.png" alt="" width="1014" height="626" srcset="https://www.percona.com/wp-content/uploads/2026/08/graph-48g.png 1014w, https://www.percona.com/wp-content/uploads/2026/08/graph-48g-300x185.png 300w, https://www.percona.com/wp-content/uploads/2026/08/graph-48g-768x474.png 768w" sizes="auto, (max-width: 1014px) 100vw, 1014px"></a><br>
<span>[ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=48" target="_blank" rel="noopener"><span>INTERACTIVE GRAPH</span></a><span> ][ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=table&amp;mem=48" target="_blank" rel="noopener"><span>TABLE</span></a><span> ]</span></p>
<p><span>In this post we are not going to talk about mechanisms behind shrinking performance gaps in 1:2 Buffer / Data size ratio.</span></p>
<p><span>Holding the entire data set in memory is not the most common thing for the database server, but in some cases it happens. Therefore, we are covering such situations as well.</span></p>
<p><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=32" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-52487" src="https://www.percona.com/wp-content/uploads/2026/08/graph-32g.png" alt="" width="1010" height="625" srcset="https://www.percona.com/wp-content/uploads/2026/08/graph-32g.png 1010w, https://www.percona.com/wp-content/uploads/2026/08/graph-32g-300x186.png 300w, https://www.percona.com/wp-content/uploads/2026/08/graph-32g-768x475.png 768w" sizes="auto, (max-width: 1010px) 100vw, 1010px"></a><br>
<span>[ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=32" target="_blank" rel="noopener"><span>INTERACTIVE GRAPH</span></a><span> ][ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=table&amp;mem=32" target="_blank" rel="noopener"><span>TABLE</span></a><span> ]</span></p>
<p><span>As the above graph shows, 8.4.10-10 is slightly ahead of 8.4.11-11, but the gap is very small.</span></p>
<p><span>This behavior is consistent with other data sizes for fully buffered data:</span></p>
<p><span>innodb_buffer_pool_size=64G and 48G Data Size:</span></p>
<p><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=64" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-52488" src="https://www.percona.com/wp-content/uploads/2026/08/graph-64g.png" alt="" width="1010" height="620" srcset="https://www.percona.com/wp-content/uploads/2026/08/graph-64g.png 1010w, https://www.percona.com/wp-content/uploads/2026/08/graph-64g-300x184.png 300w, https://www.percona.com/wp-content/uploads/2026/08/graph-64g-768x471.png 768w" sizes="auto, (max-width: 1010px) 100vw, 1010px"></a><br>
<span>[ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=64" target="_blank" rel="noopener"><span>INTERACTIVE GRAPH</span></a><span> ][ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=table&amp;mem=64" target="_blank" rel="noopener"><span>TABLE</span></a><span> ]</span></p>
<p><span>innodb_buffer_pool_size=128G and 96G Data Size:</span></p>
<p><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=128" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-52490" src="https://www.percona.com/wp-content/uploads/2026/08/graph-128g.png" alt="" width="1014" height="623" srcset="https://www.percona.com/wp-content/uploads/2026/08/graph-128g.png 1014w, https://www.percona.com/wp-content/uploads/2026/08/graph-128g-300x184.png 300w, https://www.percona.com/wp-content/uploads/2026/08/graph-128g-768x472.png 768w" sizes="auto, (max-width: 1014px) 100vw, 1014px"></a><br>
<span>[ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=graph&amp;mem=128" target="_blank" rel="noopener"><span>INTERACTIVE GRAPH</span></a><span> ][ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona.html?display=table&amp;mem=128" target="_blank" rel="noopener"><span>TABLE</span></a><span> ]</span></p>
<p><span>Again, we will not go into details about why this happens. Though it is worth noting that both 8.4.10-10 and 8.4.11-11 do better than 8.4.8-8 in all runs and configurations.</span></p>
<p><span>The table interpretation of the results is available as well.</span></p>
<p> </p>
<h2><span>4. Comparing with Upstream MySQL 8.4.11.</span></h2>
<p><span>The performance improvements in Percona Server for MySQL 8.4.11-11 are not a part of the Upstream MySQL 8.4.11. The patch was specifically designed to address the issue of Percona Server being slower than MySQL in I/O bound scenarios.</span></p>
<p><span>Also, the patch eliminated the abrupt performance degradation in the higher thread count after reaching the saturation point at 64 threads:</span></p>
<p><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona_mysql.html?display=graph&amp;mem=12" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-52500" src="https://www.percona.com/wp-content/uploads/2026/08/graph-m-12g.png" alt="" width="1015" height="624" srcset="https://www.percona.com/wp-content/uploads/2026/08/graph-m-12g.png 1015w, https://www.percona.com/wp-content/uploads/2026/08/graph-m-12g-300x184.png 300w, https://www.percona.com/wp-content/uploads/2026/08/graph-m-12g-768x472.png 768w" sizes="auto, (max-width: 1015px) 100vw, 1015px"></a></p>
<p><span>[ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona_mysql.html?display=graph&amp;mem=12" target="_blank" rel="noopener"><span>INTERACTIVE GRAPH</span></a><span> ][ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona_mysql.html?display=table&amp;mem=12" target="_blank" rel="noopener"><span>TABLE</span></a><span> ]</span></p>
<p><span>As the graph shows – Percona Server 8.4.8-8 / 8.4.10-10 was slower than MySQL in lower thread count. Although it was still faster in 128+ threads, the Percona Server was still subject to a substantial slow-down. That is where Percona Server 8.4.11-11 really shines.</span></p>
<p><span>However, with the fully buffered data MySQL goes faster than any Percona Server:</span></p>
<p><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona_mysql.html?display=graph&amp;mem=64" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-52501" src="https://www.percona.com/wp-content/uploads/2026/08/graph-m-64g.png" alt="" width="1011" height="624" srcset="https://www.percona.com/wp-content/uploads/2026/08/graph-m-64g.png 1011w, https://www.percona.com/wp-content/uploads/2026/08/graph-m-64g-300x185.png 300w, https://www.percona.com/wp-content/uploads/2026/08/graph-m-64g-768x474.png 768w" sizes="auto, (max-width: 1011px) 100vw, 1011px"></a><br>
<span>[ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona_mysql.html?display=graph&amp;mem=64" target="_blank" rel="noopener"><span>INTERACTIVE GRAPH</span></a><span> ][ </span><a href="https://percona-lab-results.github.io/ps-mysql-versions-perf/sysbench_percona_mysql.html?display=table&amp;mem=64" target="_blank" rel="noopener"><span>TABLE</span></a><span> ]</span></p>
<h2><span>5. Summary</span></h2>
<p><span>The Performance of the Percona Server 8.4 for MySQL is progressing well from older to newer version offering significant performance improvements especially in the version 8.4.11-11. This version shows very significant improvements in performance on the data sets that require I/O. Also, it outperformed the upstream MySQL 8.4.11.</span></p>
<p><span>With fully buffered data sets the version 8.4.10-10 is slightly better than 8.4.11-11. MySQL Server in this case shows the fastest performance.</span></p>
<p><span>The PGO had a positive impact demonstrating the version 8.4.10-10 being faster in all tests on all configurations than 8.4.8-8.</span></p>
<p><span>The performance depends not only on the ratio between the buffer and the data size, but also on the buffer size.</span></p>
<p>The post <a href="https://www.percona.com/blog/performance-progression-of-percona-server-for-mysql-8-4/">Performance Progression of Percona Server for MySQL 8.4</a> appeared first on <a href="https://www.percona.com/">Percona</a>.</p>]]></content:encoded>
    <pubDate>Thu, 27 Aug 2026 13:07:20 +0000</pubDate>
    <dc:creator>MySQL Performance Blog</dc:creator>
    <category>Benchmarks</category>
    <category>MySQL</category>
    <category>benchmark</category>
    <category>Percona Server for MySQL</category>
  </item>

</channel>
</rss>
